React Context API & Lifecycle methods - How to use together - javascript

I'm struggling to understand how to proceed with a small React app I am making.
I have a budget tracker, where you can add costs (mortgage, bills etc.) and they have a cost value. Each time you add, edit or delete one of these, I want the global state to change, which is stored in a context.
I basically have a 'remaining balance' value, that I want to recalculate each time something changes.
I figured I'd use a life cycle method or useEffect, but when I use that in my App.js (so that it watches for changes in all subcomponents), I can't get it to work, because the life cycle method is calling a method from my Context, but because it's not wrapped in the provider, it can't access the method in the Context.
Is this a common problem and is there are recommended way to fix it? I can't seem to find a similar problem on the GoOgLe.
App.js:
import React, { useState, useContext, useEffect } from "react";
import "./css/main.css";
import Header from "./layout/Header";
import BudgetInfo from "./components/BudgetInfo";
import PaymentForm from "./components/PaymentForm";
import CostToolbar from "./components/CostToolbar";
import Costs from "./components/Costs";
import BudgetContext from "./context/budgetContext";
import BudgetState from "./context/BudgetState";
const App = () => {
const budgetContext = useContext(BudgetContext);
const { updateBalance } = budgetContext;
useEffect(() => {
updateBalance();
});
return (
<BudgetState>
<Header darkModeToggle={toggleDarkMode} />
<main
className={"main-content" + (darkMode.darkMode ? " dm-active" : "")}
>
<div className="wrap content-wrap">
<BudgetInfo />
<PaymentForm />
<CostToolbar />
<Costs />
</div>
</main>
</BudgetState>
);
};
export default App;

You need to wrap the App component. Try the simple example.
import React, { useEffect, useContext } from 'react';
import ThemeContext from './../context/context';
const Sample = () => {
const context = useContext(ThemeContext);
useEffect(() => {
console.log(context,'--')
},[])
return(
<ThemeContext.Consumer>
{color => (
<p style={{ color }}>
Hello World
</p>
)}
</ThemeContext.Consumer>
)
}
export default Sample;

Related

how do i pass a the input value of the textfield from some component to another component in reactjs?

I am trying to pass the value of the text area from some component in reactjs to be used in another react component. the component value is stored in the first component in a useState hook so I want to access it in another component and run map() function around it . Is this possible in reactjs ? I don't want to put the whole thing in app.js because that is just plain HTML which I don't want. I want to use reactjs function components instead ?
first component:
import React, { useState, useRef, useEffect } from "react";
function Firstcomp() {
const [quotes, setQuotes] = useState(["hi there", "greetings"]);
const reference = useRef();
function sub(event) {
event.preventDefault();
setQuotes((old) => [reference.current.value, ...old]);
console.log(quotes);
return;
}
return (
<>
<div>
<div>
<div>
<h4>jon snow</h4>
</div>
<form onSubmit={sub}>
<textarea
type="textarea"
ref={reference}
placeholder="Type your tweet..."
/>
<button type="submit">Tweet</button>
</form>
{quotes.map((item) => (
<li key={item}>{item}</li>
))}
{/* we can use card display taking item as prop where it
will do the job of filling the <p> in card entry */}
</div>
</div>
</>
);
}
export default Firstcomp;
second component
import React from "react";
function SecondComp(props) {
return (
<div>
<p>{props.message}</p>
</div>
);
}
export default Secondcomp;
Use a global management state like Recoil, Redux ot Context
import React from 'react';
export const UserContext = React.createContext();
export default function App() {
return (
<UserContext.Provider value="Reed">
<User />
</UserContext.Provider>
)
}
function User() {
const value = React.useContext(UserContext);
return <h1>{value}</h1>;
}
on the exemple above we used useContext hook to provide a global variable "value", even its not declared directly in User component, but you can use it by calling the useContext hook.
in this exemple the return value in the user component is "Reed"

Custom hook's state does not update across all components?

import { useState } from 'react';
export default function usePrivacyMode() {
const [isPrivacyOn, setIsPrivacyOn] = useState(false);
return {
isPrivacyOn,
setIsPrivacyOn
};
}
This is my custom hook. I set the state in PrivacyIcons component, and then I use isPrivacyOn for show/hide values from a table based on the value. But in a different component the isPrivacyOn is not changed, it's changed only in PrivacyIcons? Why I can't change it in one component and then use the value across all components? Thanks.
states are not meant to be shared across components. You are looking for useContext. This allows you to share a function and a state between components. React has an excellent tutorial on how to do it in the official documentation: https://reactjs.org/docs/hooks-reference.html#usecontext
For your specific example it would look something like this:
Your App.js
import { useState } from 'react';
export const PrivacyContext = createContext([]);
const App = (props) => {
const [isPrivacyOn, setIsPrivacyOn] = useState(false);
return (
<PrivacyContext.Provider value={[isPrivacyOn, setIsPrivacyOn]}>
<ComponentUsingPrivacyContext />
{props.children}
</PrivacyContext.Provider>
);
};
export default App;
Keep in mind that any component that wants access to that context must be a child of PrivacyContext
Any component that wants to use PrivacyContext:
import React, { useContext } from "react";
import {PrivacyContext} from "...your route";
const ComponentUsingPrivacyContext = (props) => {
const [isPrivacyOn, setIsPrivacyOn] = useContext(PageContext);
return (
<button onclick={setIsPrivacyOn}>
Turn Privacy On
</button>
<span>Privacy is: {isPrivacyOn}</span>
);
};
export default ComponentUsingPrivacyContext;

How can I access react context in react-styleguidist's Wrapper component?

What I want:
I'm trying to add a dynamic theme option to a react-styleguidist project I'm working on. Following the idea laid out in this unfinished and closed pr, I added a custom ThemeSwitcher component, which is a select menu that is rendered in the table of contents sidebar. Selecting an option should update the brand context, which renders the corresponding theme using styled-components' BrandProvider. It should function like the demo included with the closed pr: https://fancy-sg.surge.sh/.
What's not working:
I can't access the same context in my ThemedWrapper as is provided and updated in the StyleguideWrapper and ThemeSwitcher. Examining the tree in the React Components console, it looks like react-styleguidist may render ReactExample outside of the StyleguideRenderer, which means it loses the context from the provider in that component.
Assuming I'm correct about the context not updating in ThemedWrapper due to it being located outside of StyleGuideRenderer, two high level ideas I have (but haven't been able to figure out how to do) are:
Find the correct component that is an ancestor of both StyleGuideRenderer and ReactExample in the react-styleguidist library and add the BrandProvider there so that ThemedWrapper now has context access
Some other context configuration that I haven't found yet that will allow two components to consume the same context without having a provider as an ancestor (is this possible??)
What I have:
Here are the condensed versions of the relevant code I'm using.
brand-context.js (exports context and provider, inspired by Kent C Dodds
import React, { createContext, useState, useContext } from 'react';
const BrandStateContext = createContext();
const BrandSetContext = createContext();
function BrandProvider({ children, theme }) {
const [brand, setBrand] = useState(theme);
return (
<BrandStateContext.Provider value={brand}>
<BrandSetContext.Provider value={(val) => setBrand(val)}>
{children}
</BrandSetContext.Provider>
</BrandStateContext.Provider>
);
}
function useBrandState() {
return useContext(BrandStateContext);
}
function useBrandSet() {
return useContext(BrandSetContext);
}
export { BrandProvider, useBrandState, useBrandSet };
StyleGuideWrapper.jsx (Copy of rsg-components/StyleguideRenderer, with addition of ThemeSwitcher component to toggle theme from ui; passed in styleguide config as StyleGuideRenderer)
import React from 'react';
import cx from 'clsx';
import Styled from 'rsg-components/Styled';
import ThemeSwitcher from './ThemeSwitcher';
import { BrandProvider } from './brand-context';
export function StyleGuideRenderer({ children, classes, hasSidebar, toc }) {
return (
<BrandProvider>
<div className={cx(classes.root, hasSidebar && classes.hasSidebar)}>
<main className={classes.content}>
{children}
</main>
{hasSidebar && (
<div className={classes.sidebar} data-testid="sidebar">
<section className={classes.sidebarSection}>
<ThemeSwitcher classes={classes} />
</section>
{toc}
</div>
)}
</div>
</BrandProvider>
);
}
StyleGuideRenderer.propTypes = propTypes;
export default Styled(styles)(StyleGuideRenderer);
ThemeSwitcher.jsx
import React from 'react';
import Styled from 'rsg-components/Styled';
import { useBrandSet, useBrandState } from './brand-context';
const ThemeSwitcher = ({ classes }) => {
const brand = useBrandState();
const setBrand = useBrandSet();
const onBrandChange = (e) => setBrand(e.target.value);
const brands = ['foo', 'bar'];
return (
<label className={classes.root}>
Brand
<select value={brand} onChange={onBrandChange}>
{brands.map((brand) => (
<option key={brand} value={brand}>{brand}</option>
))}
</select>
</label>
);
};
export default Styled(styles)(ThemeSwitcher);
ThemedWrapper.jsx (passed in styleguide config as Wrapper, and wraps each example component to provide them to styled-components)
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { BrandStateContext } from './brand-context';
const LibraryProvider = ({ brand, children }) => {
return (
<ThemeProvider theme={brand}>{children}</ThemeProvider>
);
};
function ThemedWrapper({ children }) {
return (
<BrandStateContext.Consumer>
{brand => (
<LibraryProvider brand={brand}>{children}</LibraryProvider>
)}
</BrandStateContext.Consumer>
);
}
export default ThemedWrapper;

Using Context API with useState in React.js, any downsides?

I create a context and a provider as below. As you can see, I use useState() within my provider (for state) along with functions (all passed within an object as the value prop, allows for easy destructuring whatever I need in child components).
import React, { useState, createContext } from "react";
const CountContext = createContext(null);
export const CountProvider = ({ children }) => {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
const decrementCount = () => {
setCount(count - 1);
};
return (
<CountContext.Provider value={{ count, incrementCount, decrementCount }}>
{children}
</CountContext.Provider>
);
};
export default CountContext;
I wrap my app within such a provider(s) at a higher location such as at index.js.
And consume the state using useContext() as below.
import React, { useContext } from "react";
import CountContext from "../contexts/CountContext";
import Incrementer from "./Incrementer";
import Decrementer from "./Decrementer";
const Counter = () => {
const { count } = useContext(CountContext);
return (
<div className="counter">
<div className="count">{count}</div>
<div className="controls">
<Decrementer />
<Incrementer />
</div>
</div>
);
};
export default Counter;
Everything is working just fine, and I find it easier to maintain things this way as compared to some of the other methods of (shared) state management.
CodeSandbox: https://codesandbox.io/s/react-usecontext-simplified-consumption-hhfz6
I am wondering if there is a fault or flaw here that I haven't noticed yet?
One of the key differences with other state management tools like Redux is performance.
Any child that uses a Context needs to be nested inside the ContextProvider component. Every time the ContextProvider state changes it will render, and all its (non-memoized) children will render too.
In contrast, when using Redux we connect each Component to the store, so each component will render only if the part of the state it is connect to changes.

React: How to call a component function on it's own render from another component?

I'm trying to implement methods to the React import of PESDK (PhotoEditorSDK).
I have an App.js that imports Header, BodyLeft and BodyMiddle without relation between them.
BodyMiddle.js is a template component that renders :
// src/components/BodyMiddle/index.js
import React, { Component } from "react";
import "./BodyMiddle.css";
class BodyMiddle extends Component {
constructor(props){
super(props);
}
handleClick(e) {
e.preventDefault();
// Nothing yet
}
render() {
return (
<div id="BodyMiddle">
<div><button id="resetEditor" onClick={(e) => this.handleClick(e)}>Reset Editor</button></div>
<div class="photo-editor-view"></div>
</div>
);
}
}
export default BodyMiddle;
PhotoEditor.js is the component that calls the PESDK :
// src/components/PhotoEditor/index.js
import React from 'react'
import ReactDOM from 'react-dom'
window.React = React
window.ReactDOM = ReactDOM
import "./PhotoEditor.css";
import "photoeditorsdk/css/PhotoEditorSDK.UI.ReactUI.min.css";
import PhotoEditorUI from 'photoeditorsdk/react-ui'
class PhotoEditor extends React.Component {
constructor(props){
super(props);
}
resetEditor(){
// Empty the image
return this.editor.ui.setImage(new Image());
}
render() {
const { ReactComponent } = PhotoEditorUI;
return (
<div>
<ReactComponent
ref={c => this.editor = c}
license='licence_removed_for_snippet'
assets={{
baseUrl: './node_modules/photoeditorsdk/assets'
}}
editor={{image: this.props.image }}
style={{
width: "100%",
height: 576
}} />
</div>)
}
}
export default PhotoEditor;
Note that the photo-editor-view div class is rendered in BodyLeft.js, by calling the following code and it works well:
ReactDOM.render(<PhotoEditor ref={this.child} image={image} />, container);
Where container is (and I pass an image somewhere else) :
const container = document.querySelector('.photo-editor-view');
What I'm trying to achieve
I would like to keep the reset Button inside BodyMiddle, which is independant and called from App.js, in order to call the PhotoEditor component on the method resetEditor() from anywhere in my app.
That way I could have separated template files that interract with each other.
I've done research and I did not really find an answer yet, I know that React might not be the lib for that, but what are the options ? I see more and more React live apps running with a lot of components interacting, I'm curious.
Thank you for your time !
Best regards
You can use ref on PhotoEditor and save that ref in App, and in the App you can have a method called onResetEditor which calls the ref.resetEditor.
Now you can pass onResetEditor to BodyMiddle or any other component.
Read more about refs in React https://reactjs.org/docs/refs-and-the-dom.html

Categories