How to update a React component based on a context array value - reactjs

Good morning,
I have been struggling with this Next.JS problem during the week, because we have a list of objects in a component that comes from the value of an array in a context.
Introduction
The issue here is:
That we have setSessionList that is a setter for a React state in the context.
sessionList that is an array of objects in the context
and the component InspectionLongList, that is the list itself, where all this objects are rendered and structured.
Question and doubts
Is it possible to conditionally update InspectionLongList when other component of the app updates a value of an object in the array sessionList, using setSessionList from the context?
Which is the good approach to do this?
Some code (for context)
The context:
import { createContext, useContext, useState } from "react";
interface IGobalState {
setSessionList: (value: any) => void;
sessionList: any;
}
export const AppContext = createContext({} as IGobalState);
export function AppWrapper({ children }: any) {
const [sessionList, setSessionList] = useState<any>([]);
return (
<AppContext.Provider
value={{
sessionList,
setSessionList,
}}
>
{children}
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
And the consumer (wrapped in the Provider)
const {setSessionList, sessionList} = useAppContext();
useEffect(() => {
if (props.auth.isLoggedIn === true){
apiService.getAllInspections(props.auth.user.accessToken).then(res => setSessionList(res))
}
}, [sessionList, setSessionList])
UPDATE: How we consume the context for updates (InspectionLongList)
useEffect(() => {
if (props.auth.isLoggedIn === true){
apiService.getAllInspections(props.auth.user.accessToken).then(res => setSessionList(res))
}
}, [JSON.stringify(sessionList)]);
Thanks in advance,
Carles

Related

React-Redux: How do I access the store in Parent Component?

So I have a React Native application and recently added Redux to it.
Now I am stuck with following problem:
There is a child component (a number slider where you can set the height value of an item) which is called by a parent component. Every time the value of the number slider in the child component changes, I want to have a console.log of the updated value in the parent component.
Therefore, the parent component somehow must have access to the Redux store, but I can't figure out how to do this. I tried converting the parent component into a Class Component and call store.getState();, but this only gives the initial value of the store and is not updated at all.
So I went back to the parent being a Functional Component and implemented the desired behavior without Redux, but with a callback function. But what do I have Redux for when I'm not using it? In the future I will definitely need Redux in this project and therefore, it would be great to solve this issue using it.
Here is the code without the callback function:
Parent.tsx
imports ...
import Child from '../../atoms/Child/Child';
import store from '../../../../index.js';
const Parent: React.FC<Props> = () => {
// console.log('store: ', store.getState());
const renderChild= () => {
return (
<View>
<Child></Child>
</View>
);
};
const dataArray = [{content: renderChild()}];
return (
<Accordion
dataArray={dataArray}
/>
);
};
export default Parent;
Child.tsx
imports ...
import {connect} from 'react-redux';
import {RootState} from '../../../rootReducer/rootReducer';
import {setHeight} from '../../../store/child/actions';
import {HeightState} from '../../../store/child/types';
type Props = {};
const Child: React.FC<Props> = () => {
const [sliderValue, setSliderValue] = useState(65);
useEffect(() => {
setHeight({height: sliderValue});
}, []);
useEffect(() => {
setHeight({height: sliderValue});
}, [sliderValue]);
return (
<View>
<Text>Height is {sliderValue} inches</Text>
<Slider
step={1}
value={sliderValue}
onValueChange={sliderValue => setSliderValue(sliderValue)}
/>
</View>
);
};
function mapStateToProps(state: RootState) {
return {
height: state.heightResult.height,
};
}
const mapDispatchToProps = {
setHeight,
};
export default connect(mapStateToProps, mapDispatchToProps)(Child);
According Redux Devtools, the store is updated correctly and works fine.
Can you please help me?
You need to connect the parent to the store also. At the moment your Parent has no idea of the store and has no relationship with it. This is the whole point of redux, flexibility and scalability of the state.
const Parent: React.FC<Props> = ({height}) => {
console.log('height', height);
};
const mapStateToProps = ({ heightResult: { height }}: RootState) => ({ height });
export default connect(mapStateToProps)(Parent);

mobx UseObserver not re-rendering on store update

I am trying to create a simple react app Using MobX, TypeScript and React Hooks, where I will type a name of a city, and on clicking an add button, it will update the list of the cities in the UI with the newly added city.
CodeSandbox demo here
The problem is, the list is not getting updated when i click the add button with a new name of a city.
Please help
There are two problems with your code:
Don't use useLocalStore (context.tsx) for creating the store, create the store by directly calling the store function, and wrap the return value of createStore function (store.ts) in an observable
// context.tsx
// const store = useLocalStore(createStore);
const store = createStore()
// store.ts
import { observable } from "mobx";
export const createStore = () => {
const store = observable({
Cities: ["Gotham"],
get allCities(): Array<string> {
return store.Cities;
},
get cityCount(): Number {
return store.Cities.length;
},
addCity: (city: string) => {
console.log("store: adding city: " + city);
store.Cities.push(city);
console.log(store.Cities);
}
});
return store;
};
export type TStore = ReturnType<typeof createStore>;
The return value of your CityView component needs to be wrapped in the useObserver function from mobx-react-lite
// city.tsx
import { useObserver } from "mobx-react-lite";
export const CityView: React.FC<{ cities: string[] }> = ({ cities }) => {
return useObserver(() => {
return (
<ul>
{cities.map((city: string) => (
<li key={city}>{city}</li>
))}
</ul>
);
});
};
code sandbox

React - update state on props changed - input element

I have already read all the stack-overflow questions related to this problem, also this official react post and the preferred solutions.
It's not recommended to use componentWillReceiveProps anymore!
Before you mark this question as duplicate, please understand my specific question, I didn't see any solution for my specific problem.
What I'm trying to do is very simple:
I have component KInputRange that received the value from props and send the value out (callback) onEnter event (will send the value to server only on enter)
The props.value can randomly change (coming by websocket from the server)
My Question:
Inside my components, the <input> value attribute will get the data from props or from state?
If from props:
How can I update the value internally when the user type input data?
If from state:
How can I update the new value if the props.value has change randomly from the server?
I'm actually need to update my internal state on props change
but how to do it today, if react says that's anti-pattern?
This my code so far:
class KInputRange extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
private onKeyDown(e: any): void {
//If the key is enter call to props.onEnter with the current value
}
private onChange(e: any): void {
//if user change the value- change the internal value
}
public render() {
return (
<input value={?????} type="text" onChange={(e) => this.onChange(e)} onKeyDown={(e) => this.onKeyDown(e)}/>
);
}
}
Usage:
<KInputRange value={this.state.dataFromTheServer} onEnter={(val: number) => this.kInputRangeEnterClicked(val)}/>
You can use a function component as mentioned in the post you linked here.
To update the value internally you can use React's State Hook.
Something like this:
import React, { useState } from 'react';
const KInputRange = (props) => {
const [value, setValue] = useState(props.value);
function onKeyDown(e: any): void {
//If the key is enter call to props.onEnter with the current value
}
function onChange(e: any): void {
setValue(e.target.value);
}
return (
<input value={value} type="text" onChange={(e) => this.onChange(e)} onKeyDown={(e) => this.onKeyDown(e)}/>
);
}
First, as #Atul said, you DO need to use getDerivedStateFromProps.
It's all because you need to control your component value depending on both - props and internal state.
Assuming you using flow this code should help:
// #flow
import * as React from "react";
type Properties = {
remoteValue: string,
onSubmit: (value: string) => void
};
type State = {
remoteValueMemo: string,
internalValue: string
};
class KInputRange extends React.Component<Properties, State> {
static defaultProps = {
remoteValue: "",
onSubmit: () => {}
};
state = {
remoteValueMemo: this.props.remoteValue,
internalValue: this.props.remoteValue
};
static getDerivedStateFromProps(props: Properties, state: State) {
if (state.remoteValueMemo !== props.remoteValue) {
return {
remoteValueMemo: props.remoteValue,
internalValue: props.remoteValue};
}
return null;
}
handleValueChange = (event: SyntheticEvent<HTMLInputElement>) => {
this.setState({internalValue: event.currentTarget.value});
};
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
if (event.keyCode === 13) {
this.props.onSubmit(this.state.internalValue);
}
};
render(): React.Node {
const {internalValue} = this.state;
return (
<input value={internalValue} onChange={this.handleValueChange} onKeyDown={this.handleKeyDown}/>
);
}
}
export default KInputRange;
The argument passed to useState is not used to update the state on re-render.
You should use the useEffect Hook.
import React, { useEffect, useState } from 'react';
const Test = (props) => {
const [value, setValue] = React.useState({...props.value});
useEffect(() => {
setValue(props.value);
}, [props.value])
//...
}
Similar topic here: React.useState does not reload state from props
You can use useEffect hook of react whenever you need to do something on specific props change in function component
useEffect(() => {
// You can place your logic here to run whenever value changes
},[value]);
[value] is a dependency so that whenever the value changes you useEffect hook calls
Hope above is helpful to you.

Is it possible to pass data up to withTracker directly from it's lower order component?

I'm working in an existing codebase that uses the React, Meteor, and react-meteor-data combo.
Everything has been going relatively fine up until I tried implementing a search feature using withTracker,
React Select,
and Meteor's subscription functionality.
import { CollectionAPI } from '../arbitrary_meteormongo_collection';
export const WriteableConnectionToCollection = withTracker(props => {
let connection = Meteor.subscribe('COLLECTION_NAME.searchByName', SEARCH_TEXT_HERE);
let isLoading = connection.ready();
return {
...props,
isLoading: isLoading,
collection: CollectionAPI.find().fetch()
}
})(PRESENTATIONAL_COMPONENT);
I've googled around and saw that a common solution for getting data to Meteor.subscribe is to use things like URL parameters, though as I am working in an existing codebase, this change would also need to be implemented in various locations.
Another way I have found is to pass the input field's value to the parent component by keeping track of the input field state in the parent component's state, though this is clearly breaking the principal of separation of concerns:
Parent Component
export const ParentComponent = React.createClass({
getInitialState() {
return {
inputFieldValue: undefined
}
},
onChange(change) {
this.setState(inputFieldValue);
},
render() {
return (
<Search
onChange={this.onChange}
inputFieldValue={this.state.inputFieldValue}
/>
}
}
withTracker HOC
import { CollectionAPI } from '../arbitrary_meteormongo_collection';
export const WriteableConnectionToCollection = withTracker(props => {
let connection = Meteor.subscribe('COLLECTION_NAME.searchByName', this.props.inputFieldValue);
let isLoading = connection.ready();
return {
...props,
isLoading: isLoading,
collection: CollectionAPI.find().fetch()
}
});
InputField Component
import { WriteableConnectionToCollection } from './connections/writeableconnection.js';
const InputFieldComponent = React.createClass({
render() {
<InputField
onInputChange={this.props.onChange}
/>
}
}
export default WritableConnectionToCollection(InputFieldComponent);
Is this the only way to do things with this particular package/framework combo or is there a simpler way that I'm just not seeing?
As Christian Fritz had mentioned in a comment under my original question, I can use ReactiveVar to be able to pass input in and out of my connection component:
export const WritableConnection = function (subscriptionName, collectionAPI) {
/**
* ReactiveVar must be outside of withTracker. If the it was inside withTracker's scope,
* anytime a user would use .set(ANY_VALUE), it would overwrite whatever was in it first,
* and then re-initialize.
**/
const input = new ReactiveVar(undefined);
return withTracker(props => {
const connection = Meteor.subscribe(subscriptionName, input.get());
const isLoading = connection.ready();
return {
...props,
isLoading: isLoading,
collection: collectionAPI.find().fetch(),
setSearchText: (text) => input.set(text),
getSearchText: () => input.get()
}
})
}

ReactJS lifecycle method inside a function Component

Instead of writing my components inside a class, I'd like to use the function syntax.
How do I override componentDidMount, componentWillMount inside function components?
Is it even possible?
const grid = (props) => {
console.log(props);
let {skuRules} = props;
const componentDidMount = () => {
if(!props.fetched) {
props.fetchRules();
}
console.log('mount it!');
};
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
Edit: With the introduction of Hooks it is possible to implement a lifecycle kind of behavior as well as the state in the functional Components. Currently
Hooks are a new feature proposal that lets you use state and other
React features without writing a class. They are released in React as a part of v16.8.0
useEffect hook can be used to replicate lifecycle behavior, and useState can be used to store state in a function component.
Basic syntax:
useEffect(callbackFunction, [dependentProps]) => cleanupFunction
You can implement your use case in hooks like
const grid = (props) => {
console.log(props);
let {skuRules} = props;
useEffect(() => {
if(!props.fetched) {
props.fetchRules();
}
console.log('mount it!');
}, []); // passing an empty array as second argument triggers the callback in useEffect only after the initial render thus replicating `componentDidMount` lifecycle behaviour
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
useEffect can also return a function that will be run when the component is unmounted. This can be used to unsubscribe to listeners, replicating the behavior of componentWillUnmount:
Eg: componentWillUnmount
useEffect(() => {
window.addEventListener('unhandledRejection', handler);
return () => {
window.removeEventListener('unhandledRejection', handler);
}
}, [])
To make useEffect conditional on specific events, you may provide it with an array of values to check for changes:
Eg: componentDidUpdate
componentDidUpdate(prevProps, prevState) {
const { counter } = this.props;
if (this.props.counter !== prevState.counter) {
// some action here
}
}
Hooks Equivalent
useEffect(() => {
// action here
}, [props.counter]); // checks for changes in the values in this array
If you include this array, make sure to include all values from the component scope that change over time (props, state), or you may end up referencing values from previous renders.
There are some subtleties to using useEffect; check out the API Here.
Before v16.7.0
The property of function components is that they don't have access to Reacts lifecycle functions or the this keyword. You need to extend the React.Component class if you want to use the lifecycle function.
class Grid extends React.Component {
constructor(props) {
super(props)
}
componentDidMount () {
if(!this.props.fetched) {
this.props.fetchRules();
}
console.log('mount it!');
}
render() {
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
}
Function components are useful when you only want to render your Component without the need of extra logic.
You can use react-pure-lifecycle to add lifecycle functions to functional components.
Example:
import React, { Component } from 'react';
import lifecycle from 'react-pure-lifecycle';
const methods = {
componentDidMount(props) {
console.log('I mounted! Here are my props: ', props);
}
};
const Channels = props => (
<h1>Hello</h1>
)
export default lifecycle(methods)(Channels);
You can make your own "lifecycle methods" using hooks for maximum nostalgia.
Utility functions:
import { useEffect, useRef } from "react";
export const useComponentDidMount = handler => {
return useEffect(() => handler(), []);
};
export const useComponentDidUpdate = (handler, deps) => {
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
return;
}
return handler();
}, deps);
};
export const useComponentWillUnmount = handler => {
return useEffect(() => handler, []);
};
Usage:
import {
useComponentDidMount,
useComponentDidUpdate,
useComponentWillUnmount
} from "./utils";
export const MyComponent = ({ myProp }) => {
useComponentDidMount(() => {
console.log("Component did mount!");
});
useComponentDidUpdate(() => {
console.log("Component did update!");
});
useComponentDidUpdate(() => {
console.log("myProp did update!");
}, [myProp]);
useComponentWillUnmount(() => {
console.log("Component will unmount!");
});
return <div>Hello world</div>;
};
Solution One:
You can use new react HOOKS API. Currently in React v16.8.0
Hooks let you use more of React’s features without classes.
Hooks provide a more direct API to the React concepts you already know: props, state, context, refs, and lifecycle.
Hooks solves all the problems addressed with Recompose.
A Note from the Author of recompose (acdlite, Oct 25 2018):
Hi! I created Recompose about three years ago. About a year after
that, I joined the React team. Today, we announced a proposal for
Hooks. Hooks solves all the problems I attempted to address with
Recompose three years ago, and more on top of that. I will be
discontinuing active maintenance of this package (excluding perhaps
bugfixes or patches for compatibility with future React releases), and
recommending that people use Hooks instead. Your existing code with
Recompose will still work, just don't expect any new features.
Solution Two:
If you are using react version that does not support hooks, no worries, use recompose(A React utility belt for function components and higher-order components.) instead. You can use recompose for attaching lifecycle hooks, state, handlers etc to a function component.
Here’s a render-less component that attaches lifecycle methods via the lifecycle HOC (from recompose).
// taken from https://gist.github.com/tsnieman/056af4bb9e87748c514d#file-auth-js-L33
function RenderlessComponent() {
return null;
}
export default lifecycle({
componentDidMount() {
const { checkIfAuthed } = this.props;
// Do they have an active session? ("Remember me")
checkIfAuthed();
},
componentWillReceiveProps(nextProps) {
const {
loadUser,
} = this.props;
// Various 'indicators'..
const becameAuthed = (!(this.props.auth) && nextProps.auth);
const isCurrentUser = (this.props.currentUser !== null);
if (becameAuthed) {
loadUser(nextProps.auth.uid);
}
const shouldSetCurrentUser = (!isCurrentUser && nextProps.auth);
if (shouldSetCurrentUser) {
const currentUser = nextProps.users[nextProps.auth.uid];
if (currentUser) {
this.props.setCurrentUser({
'id': nextProps.auth.uid,
...currentUser,
});
}
}
}
})(RenderlessComponent);
componentDidMount
useEffect(()=>{
// code here
})
componentWillMount
useEffect(()=>{
return ()=>{
//code here
}
})
componentDidUpdate
useEffect(()=>{
//code here
// when userName state change it will call
},[userName])
According to the documentation:
import React, { useState, useEffect } from 'react'
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
});
see React documentation
Short and sweet answer
componentDidMount
useEffect(()=>{
// code here
})
componentWillUnmount
useEffect(()=>{
return ()=>{
//code here
}
})
componentDidUpdate
useEffect(()=>{
//code here
// when userName state change it will call
},[userName])
You can make use of create-react-class module.
Official documentation
Of course you must first install it
npm install create-react-class
Here is a working example
import React from "react";
import ReactDOM from "react-dom"
let createReactClass = require('create-react-class')
let Clock = createReactClass({
getInitialState:function(){
return {date:new Date()}
},
render:function(){
return (
<h1>{this.state.date.toLocaleTimeString()}</h1>
)
},
componentDidMount:function(){
this.timerId = setInterval(()=>this.setState({date:new Date()}),1000)
},
componentWillUnmount:function(){
clearInterval(this.timerId)
}
})
ReactDOM.render(
<Clock/>,
document.getElementById('root')
)
if you using react 16.8 you can use react Hooks...
React Hooks are functions that let you “hook into” React state and lifecycle features from function components...
docs
import React, { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
// componentDidMount
useEffect(() => {
console.log("The use effect ran");
}, []);
// // componentDidUpdate
useEffect(() => {
console.log("The use effect ran");
}, [count, count2]);
// componentWillUnmount
useEffect(() => {
console.log("The use effect ran");
return () => {
console.log("the return is being ran");
};
}, []);
useEffect(() => {
console.log(`The count has updated to ${count}`);
return () => {
console.log(`we are in the cleanup - the count is ${count}`);
};
}, [count]);
return (
<div>
<h6> Counter </h6>
<p> current count: {count} </p>
<button onClick={() => setCount(count + 1)}>increment the count</button>
<button onClick={() => setCount2(count2 + 1)}>increment count 2</button>
</div>
);
};
export default Counter;

Resources