I am trying to practice using hooks and i am not able to wrap my head around it.
I have a single component MessageBoard component that reads the data from state which just displays a simple list of messages.
I am passing down the dispatch and state via createContext so that the child components can consume it, which in-turn uses useContext in the child components to read the value.
When the page is refreshed, I expect to see the initial UI but it fails to render that the value in the context is undefined. I have already provided the initial state to the reducer when initializing it.
App.js
import React from "react";
import MessageBoard from "./MessageBoard";
import MessagesContext from "../context/MessagesContext";
function App() {
return (
<div>
<MessagesContext>
<h2>Reaction</h2>
<hr />
<MessageBoard />
</MessagesContext>
</div>
);
}
export default App;
MessageBoard.js
import React, { useContext } from "react";
import MessagesContext from "../context/MessagesContext";
function MessageBoard(props) {
const { state } = useContext(MessagesContext);
return (
<div>
{state.messages.map(message => {
return (
<div key={message.id}>
<h4>{new Date(message.timestamp).toLocaleDateString()}</h4>
<p>{message.text}</p>
<hr />
</div>
);
})}
</div>
);
}
export default MessageBoard;
MessagesContext.js
import React, { createContext, useReducer } from "react";
import reducer, { initialState } from "../state/reducer";
export default function MessagesContext(props) {
const Context = createContext(null);
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Context.Provider
value={{
dispatch,
state
}}
>
{props.children}
</Context.Provider>
);
}
Broken Example - https://codesandbox.io/s/black-dust-13kj2
Instead if I change the MessagesContext file a bit and instead the Provider is directly injected into the App, it works as expected. Wondering what I have misunderstood here and what might be going on ?
MessagesContext.js
import { createContext } from "react";
export default createContext(null);
App.js
import React, { useReducer } from "react";
import reducer, { initialState } from "../state/reducer";
import PublishMessage from "./PublishMessage";
import MessageBoard from "./MessageBoard";
import MessagesContext from "../context/MessagesContext";
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<MessagesContext.Provider
value={{
dispatch,
state
}}
>
<h2>Reaction</h2>
<hr />
<PublishMessage />
<hr />
<MessageBoard />
</MessagesContext.Provider>
</div>
);
}
export default App;
Working Example - https://codesandbox.io/s/mystifying-meitner-vzhok
useContext accepts a context object (the value returned from React.createContext) and returns the current context value for that context.
const MyContext = createContext(null);
const value = useContext(MyContext);
// MessagesContext Not a contex object.
const { state } = useContext(MessagesContext);
In the first example:
// export the context.
export const Context = createContext(null);
export default function MessagesContext(props) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Context.Provider
value={{
dispatch,
state
}}
>
{props.children}
</Context.Provider>
);
}
and then use it:
import { Context } from '../context/MessagesContext';
function MessageBoard() {
const { state } = useContext(Context);
...
}
Working broken example:
Related
I'm trying to make movies app for portfolio, I got an api with some data and I'm trying to fetch it with useEffect and then setting the state, I also use useContext hook for passing data to children props but the data is empty.
This is App.js
import "./App.css";
import { useEffect, useState, createContext } from "react";
import Axios from "axios";
import { Main } from "./components/Main/Main";
export const AppContext = createContext();
function App() {
const [data, setData] = useState([]);
useEffect(() => {
Axios.get("http://www.omdbapi.com/?s=star wars&apikey=459f1ce1").then((res) => {
setData(res.data.Search);
})
}, []);
return (
<div className="App">
<AppContext.Provider value={data}>
<Main />
</AppContext.Provider>
</div>
);
}
export default App;
This is Main.js
import React from "react";
import { useContext } from "react";
import { AppContext } from "../../App";
import "./Main.css";
export const Main = () => {
const { data } = useContext(AppContext);
console.log(data)
return <div>
</div>;
};
export default Main;
Currently the value of the context is just the value of the data state. To be able to destructure the context change your value to an object containing the data.
<AppContext.Provider value={{ data }}>
props are passing fine when we are passing them as a whole array of objects but it is not working when I am passing the props by traversing through the array using map function.
import { React, useEffect, useState } from "react";
import axios from "axios";
import "./Home.css";
import Cardimg from "./Cardimg";
const Home = props => {
return (
<>
<div className="header">PHOTO GALLERY</div>
<div className="photos">
{props.data?.map(e => {
<Cardimg data={e.ImgUrl}></Cardimg>;
})}
</div>
</>
);
};
export default Home;
in the above code props are passing when I am passing manually in Cardimg component...but as soon as I start using map then it doesn't work...like the props are not reaching the component.
below is my Cardimg component
import React from 'react'
const Cardimg = (props) => {
console.log(props.data);
return (
<div>{props.data}</div>
)
}
export default Cardimg
You need to return the Cardimg component inside map callback function.
Either like this
{
props.data?.map(e => {
return <Cardimg data={e.ImgUrl}></Cardimg>;
});
}
Or like this
{
props.data?.map(e => <Cardimg data={e.ImgUrl}></Cardimg>)
}
I am trying to make a React project which fetches data from an API, and I'm trying to use useReducer hook with useContext from an external file.
Folder Structure:
public
src (and in src folder, I have context.js and reducer.js along with rest of the components and and other files)
package.json
reducer.js:
const reducer = (currentState, action)=>{
switch(action.type){
case 'setBreedList':
// console.log('setBreedList ran')
return {...currentState, breedList: [...currentState.breedList, action.payload]}
// return currentState
// return {...currentState.breedList, action.payload}
default:
return currentState
}
}
export default reducer
context.js
import React, {useContext, useReducer} from 'react';
import reducer from './reducer';
const AppContext = React.createContext();
const initalState = {
breedList: [],
imageList: []
}
const AppProvider = ({children})=>{
const [state, dispatch] = useReducer(reducer, initalState);
const fetchAllBreeds = ()=>{
fetch('https://dog.ceo/api/breeds/list/all')
.then(res=>res.json())
.then(data=>dispatch({type: 'setBreedList', payload: data}))
}
return (
<AppContext.Provider value={{...state, fetchAllBreeds}}>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = ()=>{
return useContext(AppContext)
}
export {AppContext, AppProvider}
and this is index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {AppProvider} from './utils/context';
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById('root')
);
and finally, this is Input.jsx, where I call the fetchAllBreeds function:
import React from 'react'
import {useGlobalContext} from '../utils/context'
const Input = () => {
const {state, fetchAllBreeds} = useGlobalContext();
fetchAllBreeds();
console.log('hello')
return (
<div>
<p>hello</p>
</div>
)
}
export default Input
Now, when I try to call the fetchAllBreeds function in the Input.jsx file above, it prints on the console, 'hello', endlessly. I can't figure out why this is happening and I have tried some different things, but the result is the same. Will be grateful to your help.
Input component is calling fetchAllBreeds function, that will update the context value which in turn will re-render Input component, hence fetchAllBreeds will re-run. Don't call any method inside the component body which will cause re-renders.
Use useEffect hook inside Input component. which will execute only once.
import React, {useEffect} from 'react'
import {useGlobalContext} from '../utils/context'
const Input = () => {
const {state, fetchAllBreeds} = useGlobalContext();
useEffect(()=>{
fetchAllBreeds();
}, [])
return (
<div>
<p>hello</p>
</div>
)
}
export default Input
I'm trying to create a darkmode library (named react-goodnight) based on https://github.com/luisgserrano/react-dark-mode.
This is where the context is created.
import React from 'react'
const ThemeContext = React.createContext({
theme: '',
toggle: () => {}
})
export default ThemeContext
This is my useDarkMode hook that get/sets the theme to localStorage.
import { useState, useEffect } from 'react'
const useDarkMode = () => {
const [theme, setTheme] = useState('light')
const setMode = (mode) => {
window.localStorage.setItem('theme', mode)
setTheme(mode)
}
const toggle = () => (theme === 'light' ? setMode('dark') : setMode('light'))
useEffect(() => {
const localTheme = window.localStorage.getItem('theme')
localTheme && setTheme(localTheme)
}, [])
return [theme, toggle]
}
export default useDarkMode
This is the index of my library (react-goodnight).
import React, { useContext } from 'react'
import { ThemeProvider } from 'styled-components'
import { GlobalStyles } from './globalStyles'
import { lightTheme, darkTheme } from './settings'
import ThemeContext from './themeContext'
import useDarkMode from './useDarkMode'
const Provider = ({ children }) => {
const [theme, toggle] = useDarkMode()
return (
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
<GlobalStyles />
<ThemeContext.Provider value={{ theme, toggle }}>
<button onClick={toggle}>Toggle</button>
{children}
</ThemeContext.Provider>
</ThemeProvider>
)
}
export const useDarkModeContext = () => useContext(ThemeContext)
export default Provider
And, in the end, this is my example app where I'm trying to use it.
import React from 'react'
import Provider, { useDarkModeContext } from 'react-goodnight'
const App = () => {
const { theme, toggle } = useDarkModeContext();
console.log(theme)
return (
<Provider>
<div>hey</div>
<button onClick={toggle}>Toggle</button>
</Provider>
)
}
export default App
The "Toggle" button in the library's index works fine but the one in my example app does not.
The useDarkModeContext() returns empty.
What could be the issue?
Thanks!
You are doing wrong
1st option
you can use react-goodnight provider with your index.js and use useDarkModeContext(), don't name your index.js Provider else you can not use Provider coming from react-goodnight
import Provider, { useDarkModeContext } from 'react-goodnight'
const Provider = ({ children }) => {
const [theme, toggle] = useDarkMode()
return (
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
<GlobalStyles />
<Provider>
<ThemeContext.Provider value={{ theme, toggle }}>
<button onClick={toggle}>Toggle</button>
{children}
</ThemeContext.Provider>
</Provider>
</ThemeProvider>
)
}
2nd Option
you are passing ThemeContext in your index.js so you can also access that in app.js
import React, { useContext } from 'react'
import ThemeContext from './themeContext'
const App = () => {
const theme = useContext(ThemeContext);
console.log(theme)
return (
<Provider>
<div>hey</div>
<button onClick={toggle}>Toggle</button>
</Provider>
)
}
export default App
The reason it's not working is because you are calling useContext in the very same place where you print Provider.
Why is that wrong? Because useContext looks for parent context providers. By rendering Provider in the same place you call useContext, there is no parent to look for. The useContext in your example is actually part of App component, who is not a child of Provider.
All you have to do is move the button outside of that print, to its own component, and only there do useContext (or in your case the method called useDarkModeContext.
The only change would be:
import React from 'react'
import Provider, { useDarkModeContext } from 'react-goodnight'
const App = () => {
return (
<Provider>
<div>hey</div>
<ToggleThemeButton />
</Provider>
)
}
export default App
const ToggleThemeButton = () => {
const { theme, toggle } = useDarkModeContext();
return (
<button onClick={toggle}>Switch Theme outside</button>
);
};
In my SPA, I am utilizing react hooks and context API. I need to persist the current state of the component view rendered using the context API so that I can implement the global component conditional rendering through the application.
I have two views on a single dashboard page: overview & detail. The button triggers the global state change and the view should be fixed on the state value even on page refresh.
Here's my code snippets:
AppRoutes file
import React, { useState } from "react";
import { Router, Route, Switch } from "react-router-dom";
import history from "../utils/history";
import { PyramidProvider } from "../context/pyramidContext";
import Dashboard from "../pages/dashboard/Dashboard";
const AppRoutes = () => {
return (
<div>
<React.Suspense fallback={<span>Loading...</span>}>
<Router history={history}>
<Switch>
<PyramidProvider>
<Route path="/" component={Dashboard} />
</PyramidProvider>
</Switch>
</Router>
</React.Suspense>
</div>
);
};
export default AppRoutes;
Dashboard page
import React, { useState, useEffect, useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
import PyramidDetail from "../../components/pyramidUI/pyramidDetail";
import PyramidOverview from "../../components/pyramidUI/pyramidOverview";
const Dashboard = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<React.Fragment>
{info.uiname === "overview" ? <PyramidOverview /> : <PyramidDetail />}
</React.Fragment>
);
};
export default Dashboard;
Overview component
import React, { useState, useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
const Overview = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<div className="d-flex flex-column dashboard_wrap">
<main>
<div className="d-flex">
<button
onClick={() => setInfo({ uiname: "detail", pyramidvalue: 1 })}
>
change view
</button>
</div>
</main>
</div>
);
};
export default Overview;
Detail component
import React, { useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
// import axios from "axios";
const Detail = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<div className="d-flex flex-column dashboard_wrap">
<h2>Detail View</h2>
<div>
<button
type="button"
onClick={() => setInfo({ uiname: "overview", pyramidvalue: 0 })}
>
Back
</button>
</div>
</div>
);
};
export default Detail;
Context File
import React, { createContext, useEffect, useReducer } from "react";
let reducer = (info, newInfo) => {
return { ...info, ...newInfo };
};
const initialState = {
uiname: "overview",
pyramidvalue: 0,
};
const localState = JSON.parse(localStorage.getItem("pyramidcontent"));
const PyramidContext = createContext();
function PyramidProvider(props) {
const [info, setInfo] = useReducer(reducer, initialState || localState);
useEffect(() => {
localStorage.setItem("pyramidcontent", JSON.stringify(info));
}, [info]);
return (
<PyramidContext.Provider
value={{
info,
setInfo,
}}
>
{props.children}
</PyramidContext.Provider>
);
}
export { PyramidContext, PyramidProvider };
I click the button to render a detail view and soon as the page is refreshed, the component changes its view to overview instead of sticking around to detail. I checked the local storage values, and it is being updated properly, but still, the component view does not persist as per the value.
I am unable to understand where I am doing wrong, any help to resolve this issue, please? Thanks in advance.
You're never using the value of localStage in your info state,
you should replace your code with:
const [info, setInfo] = useReducer(reducer, localState || initialState);