React Context always returns EMPTY - reactjs

I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v

I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.

You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store

Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.

Related

How to re-render a parent from a children component with hook?

I would like to force my parent to re-render the page when I click on a button from a child component.
(I don't use redux because I don't have the time to learn it in my project, so I use localStorage. Unfortunately React don't see when a change is operated on local Storage, so he don't re-render. It's why I would like to force it to re-render my page (to have the right content).)
I tried to use hook with the function useState to do it but it's not working and I don't know why...
(Nothing change in my page)
This is my parent page: (just the code important)
const[reload, setReload] = useState(false);
...
else if (user) { contents = [<Message_UserIdentified user={user} callBack={setReload}/>, contentform]; }
This is my child component:
const Message_UserIdentified = (props) => {
let user = props.user;
return (
<Alert color="primary" className="alert alert-dismissible alert-info">
<h4>Welcome {!user ? "" : user.firstname} {!user ? "" : user.lastname}</h4>
If you are not {!user ? "" : user.firstname} click <a onClick={() => {localStorage.removeItem('idUser'); props.callBack(true);}}>here.</a>
</Alert>
);
}
Why my parent page don't want re-render ?
Thanks in advance.
I have created a proof of concept of what you are trying to achieve and it works:
https://codesandbox.io/s/weathered-smoke-ojr5j
probably there's something else in your code that we can't see that's preventing the component to re render
Your child component can have a prop which directly pass setReload to it.
However one common usage is that, setReload can be associated with an event, ex. onReload. You can pass a prop onReload to the child instead.
<Child onReload={() => { setReload() }} />
Inside onReload implementation, you can call setReload.
The reload state variable in your parent component is strictly local to it; the child can't see it.
I've been using React Hooks for about 2 months now. The learning curve, at times, has been steep but I'm now getting really proficient at it.
A companion technology to Hooks called Context API is perfect for your needs. It's what you should be using rather than local storage because both components can access it. Your child component would set the equivalent of reload in the Context to true and your parent would have a useEffect function that would have reload as a dependency. Thus, when reload is changed from false to true, the useEffect function in the parent would be fired and run the code you desire.
Early on, I very much benefitted from this video series: https://www.youtube.com/watch?v=6RhOzQciVwI&t=46s Watch the first few videos and you should quickly understand how to implement the Context API in your functional React components.

passing mapped data from an api to another component

so i have data that i get from an api in componentDidMount, and then i map over it in the render. lets say the map returns 4 objects. How do i create a button click event that captures that specific objects data, and passes it along a route to another component?
code:
clickEvent(){
???
}
this.example = this.state.data.slice(1).map((data, key) =>
<div key={item.Id}>
<div>{data.dataIWantToPass}</div>
<Link to='/next_component_path' onClick={clickEvent}}>Click</Link>
</div>
So lets say the above returns 4 objects. I want the third one, when the link is clicked to pass its data.
clickEvent(dataUWantToPass){
<NewComponent dataPassed={dataUWantToPass} />
}
this.example = this.state.data.slice(1).map((data, key) =>
<div key={data.Id}>
<div>{data.dataIWantToPass}</div>
//Link is a react-router-dom component, this helps you to redirect to other component (to which you have added the route for
<Link to='/next_component_path'>
<button onClick={()=>this.clickEvent(data.dataIWantToPass)} value="Click"/>
</Link>
</div>
))
You can receive the data in the NewComponent as props.
If you want to check than you can write the componentWillReceiveProps() method in the NewComponent as:
componentWillReceiveProps(reveivedProps){
console.log(reveivedProps);
}
Ok, I have solved this by piecing together various bits from comments, so thank you for your contributions. This is how it was resolved:
On the link tag i did this:
<Link to={{ pathname: '/path', query:{passed_id: data.id} }}></Link>
Then on the component that the path routes to:
this.setState({passed_id: this.props.location.query.passed_id});
And that gave me access to the data i was passing, in this case an ID. Now I just need to figure out how to compare that ID, with the looped data I also pass (via local storage), and map over it. Another question i suppose.
You need to use redux. This allows you to store data in global application store, and get the data from any component which is subscribed to the store. Today almost every react project needs redux to manage the data across the application

How to globally disable/hide/replace a component by name in React?

I have a large React app and I have a few components that I would like to completely disable from a config or global level. Is there any kind of global hook that I can use that is called before any component is rendered? If so, I imagine I can check the name of the component and return null if the name is on the disabled list. How would you do this?
There are a lot of ways to do this:
React's Context API allows you pass props through every level of the component tree so you can use them as flags to enable/disable components. Should be used sparingly however.
Higher Order Components are basically just functions that return a component. You could wrap your components in logic to render them as needed.
Or of course you could use a global state manager like redux to set global states.
There are many ways to do this, so, I'll just describe one simple way: using references and updating the states accordingly.
Full working feature hide/showing sandbox online: codesandbox.io ReactJS Feature Hide/Show Demo
Defined are two classes, class Feature extends React.Component and class App extends React.Component. The render() for <Feature/> is...
render() {
if (!this.state.enabled) {
return <div />;
}
return (
<div className="Feature">
<h1>My Feature!</h1>
</div>
);
}
And the option for enabling/disabling a feature in <App /> would handle display/hiding like so...
handleOnClick(e) {
if (e.target.checked) {
this.feature.setState({ enabled: true });
} else {
this.feature.setState({ enabled: false });
}
}
Of course, you need to make sure that <Feature /> has the reference set...
<Feature
ref={instance => {
this.feature = instance;
}}
/>
If you need simplest solution just use browser global vars and check it in render.
render() {
if( window.globalFlag ) return null
return (
<div> feature content...
Drawbacks:
modifying component,
using global scope,
some unnecessary code can be run earlier (f.e. constructor) and later (f.e. componentDidMount).
Use HOCs - wrap your component - connecting with global store using redux or context API.
<FlagsProvider store={flagStore}>
<SomeComponent_1>
<SomeComponent_2>
<FlagsConsumer flag="someFeatureFlag">
<SomeFeatureComponent />
<FlagsConsumer/> connects to store (redux connect would be an inner wrapper - composing HOCs) and conditionally renders <SomeFeatureComponent /> (or null).
Of course HOC can pass received props to wrapped component - it can be functionally transparent.
Don't reinvent the wheel - use some ready module, read tutorials, google for sth suitable.
HOC can also play a role of A/B testing.

Will ReactDOM.hydrate() trigger lifecycle methods on the client?

From the React 16 docs about ReactDOM.hydrate(),
Same as render(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
Will ReactDOM.hydrate() also trigger lifecycle methods on the client such as componentWillMount(), componentDidMount() during initial render?
Will render() method be called on the client during hydration? I suppose not, because that's the difference between ReactDOM.render() and ReactDOM.hydrate()?
If render method won't be called on the client, we wouldn't expect componentDidMount() lifecycle method to be triggered.
If none of the lifecycle methods are called on the client, how would we know when has React finished rendering. I suppose the callback in the following syntax:
ReactDOM.hydrate(element, container[, callback])
I want to understand if there are lifecycle methods / hooks (which give more control over the application) available when React is "attempting to attach event listeners to existing markup".
Since ReactDOM.hydrate is (and should be) called on the client then YES it is supposed to run componentDidMount. componentWillMount is already called when rendered on the server.
componentDidMount does not run on the server, therefore when you call hydrate, the app runs the event.
Think about hydrate as a different render method. It does render but not in the same way. It looks for mismatches between your server rendered React and your client React. It does not render everything again.
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content (such as timestamps), but you should treat mismatches as bugs and fix them
However you might want to do some crazy stuff like rendering something completely different on the client side (than what was rendered on the server). For this pay attention to this paragraph
If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a state variable like this.state.isClient, which you can set to true in componentDidMount(). This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. Note that this approach will make your components slower because they have to render twice, so use it with caution.
So as you can see it does a render pass. If there are no mismatches React is optimized for that.
I hope it was clarifying. I speak from experience with React SSR and basic understanding of reading the docs.
The rendered elements probably aren't same between server and client, because initially the elements are rendered into texts at the server in memory, therefore they are not mounted. When the content is moved to client, it can be re-attached to react via hydrate which is fake "render" to wire with the rest of react functionalities, such as events.
In order to tell when it's hydated, here's a piece from internet which I found clearly stated the above rational. https://dev.to/merri/understanding-react-ssr-spa-hydration-1hcf?signin=true
const HydrateContext = createContext('hydrated')
export function useIsHydrated() {
return useContext(HydrateContext)
}
export function IsHydratedProvider({ children }) {
const [isHydrated, setIsHydrated] = useState(false)
useEffect(() => {
setIsHydrated(true)
}, [])
return (
<HydrateContext.Provider value={isHydrated}>
{children}
</HydrateContext.Provider>
)
}
To use it,
function MyComponent() {
const isHydrated = useIsHydrated()
return !isHydrated ? 'Initial render' : 'SPA mode'
}
function App() {
return (
<IsHydratedProvider>
<MyComponent />
</IsHydratedProvider>
)
}
It feels to me, any rendered component teleports from the server to the client.
p.s Here's another article which talks about the second render after the mount, https://medium.com/swlh/how-to-use-useeffect-on-server-side-654932c51b13
I read the type of ReactDOM.hydrate in TypeScript system:
(
element: SFCElement<any> | Array<SFCElement<any>>,
container: Container| null,
callback?: () => void
): void;
And example to the above declaration:
ReactDOM.hydrate(
<App />, // element
document.getElementById('root'), // container
() => { // callback
/* do what you want after hydration */
}
);

React.js shouldComponentUpdate() and react-router Link

I currently have a doubt about the correct combined implementation of react-router Link navigation and shouldComponentUpdate() on the root application level.
That is, I have a root component called App.jsx which contains a global component with a header, footer, sidebar etc and this same component has an ajax long-poll which retrieves new registrations in the system and updates the state when new users register.
Since I don't want to push a re-render to the component (and therefore all it's children) on ajax responses that don't have updates I decided to make use of the lovely shouldComponentUpdate() method.
So, I came up with something like this - noting that I'm making use of lo-dash:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState);
}
With this the component correctly ignores irrelevant responses about the latest registrations.
Now, the problem appears when I have to make the routing. To clarify before, this is the kind of structure of the render():
Note: the _routerTransitionKey is just a helper I have to not make transitions when I'm navigating internal views state and it's working correctly.
<Grid key='app' id="wrapper" className="no-padding">
<Header user={this.state.user} allRegistrations={this.state.allRegistrations}/>
<section id="page-wrapper">
<NotificationArea key='internalNotification' />
<RouteHandler key={_routerTransitionKey} user={this.state.user} allRegistrations={this.state.allRegistrations}/>
</section>
</Grid>
Because I have the RouteHandler inside this global component, I have the issue that a change in the route is completely ignored by it, since the application state itself didn't change. That causes the component to never trigger the render() on navigation and therefore never update the RouteHandler.
What I needed would be something like:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState) || ROUTE_CHANGED ;
}
My question is: does anybody out there knows of a clever approach to this issue? I'm trying to avoid having to create yet another wrapping component to handle the Routes before they reach this App component I currently have...
So, after the tip from #WayneC, even though the react-router doesn't inject the props directly into the react component props, there's a possible way inspired by that approach.
I achieved what I wanted by doing a slight change using not the this.props, but instead the this.context.router.getCurrentPath()
So now the solution looks like this:
shouldComponentUpdate (/*nextProps*/, nextState) {
return !_.isEqual(this.state,nextState) || this.context.router.getCurrentPath() !== _routerTransitionKey;
}
Just to make it clearer, my _routerTransitionKey gets its value from an imported Util that looks mostly like this:
var Utils = {
Router: {
TransitionKey: {
get: function(){
return Router.HistoryLocation.getCurrentPath();
}
}
}
}
_routerTransitionKey = Utils.Router.TransitionKey.get();
This _routerTransitionKey is scoped in an upper level, and I modify it on every render(), so that I keep track of it for later comparison.
And... that's it.

Resources