I am trying to pass data from a JSON file to a component with useContext:
import sessionData from '../session.json';
import React from 'react';
export const SessionContext = React.createContext(sessionData);
export const SessionProvider = SessionContext.Provider;
export const SessionConsumer = SessionContext.Consumer;
console.log('session data', sessionData); //console log ok
Next I am passing it into a class component
static contextType = SessionContext;
componentDidMount() {
const sessId = this.context;
console.log('steps', sessId); // log ok
}
render() {
return <SessionProvider value={sessId}> //value returns undefined.
<Child A />
<Child B />
<Child C />
</SessionProvider>;
}
In the first two code blocks shown, my data returns fine. Now I want to pass it to the child components (which are functional component). However, when I try to pass a value to the session provider <SessionProvider value={sessId}, the value returns as undefined.
I've seen other examples where the value is passed directly into the component like ``{sessId.name}. However, if I'm just trying to make sessId``` available to all the child components.
sessId doesn't exist
The error that you're getting is likely that sessId doesn't exist.
sessId exists in the componentDidMount method but not in your render method.
You would need to create that variable in your render method with this.context, just like you did in componentDidMount.
You don't need the provider
But... you don't need the provider there. When you provide an initial value to React.createContext, then that becomes a default value, so you can just consume it with the children and the data will be there.
You don't need context
However, if you're just using static JSON, then you don't need context here at all: just import the JSON file into each of the children and use it there.
Related
I have tried pass value from parent to grandchild component, and it works. While I am thinking if there is another simpler or other way of passing props in shorter path.
What I did is quite cumbersome in codesandbox
There may be a common problem in react world called prop drilling by passing data to children only using props.
I would recommend only 2-level passing, if you need pass data deeper then you probably doing something wrong.
Use one of popular state management library (if your project is big) or React context (which is awesome)
Create a folder called /contexts and put contexts there. The structure of files can be like shown below:
First you need to create a context itself
type ClientContextState = {
data: User;
set: (data: User) => void;
logout: () => void;
};
// empty object as a default value
export const UserContext = createContext<UserContextState>({} as UserContextState);
Then create a Provider wrapper component
export const UserProvider = ({ children }: Props) => {
const [data, setData] = useState<User>({});
const sharedState = {
data,
set: setData
logout: () => setData(null)
}
return <UserContext.Provider value={sharedState}>{children}</UserContext.Provider>
});
You may also want to have an alias of useContext hook:
export const useUser = () => {
return useContext(UserContext);
};
After all this whenever you wrap your components or app to <UserProvider>...</UserProvider> you can use our hook to access data and methods form sharedState from any place you want:
export LogoutButton = () => {
const {data, logout} = useUser();
return <Button onClick={() => logout()}>Logout from {data.name}</Button>
}
Whenever you want to pass props or data from Grandparent to child component, always use react-redux. This is useful to maintain the state and access the data from anywhere/any component.
Another way is to use useContext hooks which you can use to pass the props
Following are the steps to use useContext hooks
Creating the context
The built-in factory function createContext(default) creates a context instance:
import { createContext } from 'react';
const Context = createContext('Default Value');
The factory function accepts one optional argument: the default value.
Providing the context
Context.Provider component available on the context instance is used to provide the context to its child components, no matter how deep they are.
To set the value of context use the value prop available on the
<Context.Provider value={value} />:
function Main() {
const value = 'My Context Value';
return (
<Context.Provider value={value}>
<MyComponent />
</Context.Provider>
);
}
Again, what’s important here is that all the components that’d like later to consume the context have to be wrapped inside the provider component.
If you want to change the context value, simply update the value prop.
Consuming the context: Consuming the context can be performed in 2 ways.
The first way, the one I recommend, is to use the useContext(Context) React hook:
import { useContext } from 'react';
function MyComponent() {
const value = useContext(Context);
return <span>{value}</span>;
}
Generally it's helpful to consider whether moving state down the hierarchy would be the simplest route. That means lifting the component instantiation to a place closer to the state being used. In your example, that could mean Component_data is used inside Component and passed to its children there, removing one step in the nested data flow. Even better, would be that Child.a accesses Component_data.A directly.
In a real app with cases where accessing the data directly is less feasible, a solution I lean towards is using Context to set data in the parent that retrieves it, and then I can access it however deeply nested the component might be that needs it.
i.e. in App I would create the Context provider, and in ChildA I access it via useContext hook.
Further reading
https://reactjs.org/docs/context.html
https://overreacted.io/before-you-memo/#solution-1-move-state-down (this post is about an alternative to using useMemo but has an illustrative example of why moving state down is a good thing)
I'm quite new with reactjs, and I'm struggling with the following:
I have a Header component (in my App.js file) which contains an AudioPlayer. This AudioPlayer needs to be updated with current value of src (with the path of the song, stored in local) and title. Now, think about something like this:
App.js
function App(){
<Header/>
<Page1/>
<Page2/>
<Page3/>
...
<SomePage/>
...
<Footer/>
}
Header.js
import HeaderLogic from './HeaderLogic'
funtion Header(){
const {property1, property2, ..., path, title} = HeaderLogic()
//In HeaderLogic I want to get path and title, set in SomePageLogic.js
...
return <>
<AudioPlayer
src={path}
title={title}
/>
...
</>
}
SomePage.js
import SomePageLogic from './SomePageLogic'
import songPath from './somePath/songPath'
function SomePage(){
const title = 'songTitle'
const {property1, property2, ..., propertyN} = SomePageLogic(songPath, title)
//In SomePageLogic (or maybe here) I want to set path and title of the song,
//in order to be used from the HeaderLogic component
return <>
...
</>
}
I'm not sure useContext could be fine in this case, since I want to share data between unrelated components... Of course, the Header should rerender when it detects the song has been set. Should I use some kind of subscription pattern?
Any hint?
Thank you very much!
you can use Redux for storing your states. Basically React Redux is kind of a store where you can store your state and whenever it get updated it directly render html. you can access store state anywhere by using useSelector in hook, it get updated value whenever it changed by some other component
No need for any subscription pattern and definitely not using localStorage - context is exactly used for your usecase: sharing state between unrelated components.
So for instance, I want to share selected menu between the header and the Page1 somewhere below, you'd create a context with createContext(), export it's provider, wrap the children, and then you share that state between all components you set as children. Example:
const defaultSelectedMenu = 'a';
export const MenuContext = createContext({ selectedMenu: defaultSelectedMenu });
export const MenuProvider = ({ children }) => {
const [selectedMenu, setSelectedMenu] = useState(defaultSelectedMenu);
return (<MenuContext.Provider value={selectedMenu, setSelectedMenu}>
{children}</MenuContext.Provider>)
}
And then in your app.js:
...
<MenuProvider>
<Header />
<Page1 />
</MenuProvider>
<Page2 />
...
Now, your Header and Page1 components can access the MenuContext through useContext(MenuContext) and have access to the state and the mutator.
This was just a simple example but the same logic applies to any logic you want to share, heavy computation, API calls, you name it. You define the implementation in the <Provider> and then consume the implementation by accessing it through useContext
You can use localStorage.
Basically, it'll store your desired value in the browser. So, you can access it from anywhere within the application.
Scenario
Depending on the data coming into props I may want my component to render but I also may NOT want my component to rerender.
Code
Currently I'm using if(!props.data) return null but this merely sends back a blank component.
Question
How can I forfeit the render so that my current dom element stays unchanged, leaving the last rendered content intact? I'm wanting my component only update when props.data has a truthy value.
You can store the data received in a local state and only update it if props.data changes and has a truthy value like this:
import React, {useEffect, useState} from 'react';
const MyComponent = ({data}) => {
const [localData, setLocalData] = useState(data);
useEffect(() => {
if (data) setLocalData(data);
}, [data]);
return (
<div>{localData}</div>
);
};
But note that this has a little bit of a design smell. Why does your component receive new props in the first place if it shouldn't update. The correct place to hold that state may be in a component further up the tree.
The simple way is to try using conditional rendering of the component if the data is present in the parent instead of checking the props within the component.
function ParentComponent(props) {
const data = <your custom data>;
if(data) {
return <ChildComponent data={data} />;
}
}
I need a component that starts polling for data from an API as soon as it gets mounted and it stops when unmounted. The data must be available for a child component.
This is my current implementation skeleton
class External extends Component {
render() {
const ConnectedPoller = connect(/*cut*/)(Poller)
return <ConnectedPoller {...this.props}>
{this.props.children}
</ConnectedPoller>
}
}
class Poller extends Component {
componentDidMount() {
this.model.startPolling();
}
componentWillUnmount() {
this.model.stopPolling();
}
render() {
const children = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {...this.props});
});
return children;
}
}
There are a few components that need these data, I can use my poller as their parent in this way
<External>
<ComponentRequiringData {...this.props}>
</External>
This is working, but I would like to merge External into Poller. The problem is that I cannot find a way to do this
const ConnectedPoller = connect(/*cut*/)(this.props.children)
The connect function complains when I pass props.children instead of a "real" react element. Any suggestion?
DISCLAIMER: I don't poll at an ancestor level because it's a heavy resource-consuming task for the backend and I need those data only in a few components that are used rarely and are never rendered together in the same page.
The only reason to use redux' connect function, is if you want to connect to the state manangement - either get the current state, or dispatch an action. Since you aren't doing any of these here, there is no reason to use connect. Also, while it might work, there is no reason to use connect inside a rendering function. This will recreate the commponent over and over again for no reason. I guess you are doing it because you want to pass the component with props, but connect can receive props from a parent element as a second argument.
Let's say we have a context provider set up, along with some initial data property values.
Somewhere along the line, let's say a consumer then modifies those properties.
On page reload, those changes are lost. What is the best way to persist the data so we can retain those data modifications? Any method other than simply local storage?
Yeah, if you want the data to persist across reloads, your options are going to be storing that info server-side (via an api call) or in browser storage (local storage, session storage, cookies). The option you'll want to use depends on what level of persistence you're looking to achieve. Regardless of storage choice, it would likely look something along the lines of
const MyContext = React.createContext(defaultValue);
class Parent extends React.Component {
setValue = (value) => {
this.setState({ value });
}
state = {
setValue: this.setValue,
value: localStorage.getItem("parentValueKey")
}
componentDidUpdate(prevProps, prevState) {
if (this.state.value !== prevState.value) {
// Whatever storage mechanism you end up deciding to use.
localStorage.setItem("parentValueKey", this.state.value)
}
}
render() {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
)
}
}
Context doesn't persist in the way you want. Here's a sample of what I've done, using stateless functional with React hooks.
import React, {useState, useEffect} from 'react'
export function sample(){
// useState React hook
const [data, setData] = useState({})
const [moreData, setMoreData] = useState([])
// useState React hook
useEffect(() => {
setData({test: "sample", user: "some person"})
setMoreData(["test", "string"])
}, [])
return data, moreData
}
export const AppContext = React.createContext()
export const AppProvider = props => (
<AppContext.Provider value={{ ...sample() }}>
{props.children}
</AppContext.Provider>
)
Understand from the start that this isa workaround, not a permanent solution. Persisting data is the job of a database, not the client. However, if you need persisted data for development, this is one way. Notice first that I'm using React hooks. This is a fully supported feature as of 16.8. The useEffect() replaces the lifecycle methods found in class declarations like that of TLadd above. He's using componentDidUpdate to persist. The most up-to-date way of doing this is useEffect. When the app is refreshed this method will be called and set some hard-coded data in context.
To use the provider:
import React from 'react'
import Component from './path/to/component'
import { AppProvider } from './path/to/context'
const App = () => {
return (
<AppProvider>
<Component />
</AppProvider>
)
}
When you refresh, data and moreData will still have whatever default values you assign to them.
I am assuming that you are already familiar with setting context and setting up the context provider.
One of the things you can do is to store the value in the browser's Cookie or any storage available to you, and then, in your Context Provider, retrieve the value, if you can find it, you set it, if not, set the default. If the provider file is a class based component, you would like to retrieve this value in the constructor(), otherwise if it is functional, you can use useLayoutEffect() to set it.