Can't get the first value by using useState in a functionn - react-hooks

I need to show the text according to the data value. By running the code, I want to see the 'Test1: 1' can be shown after I clicked the button, but I can't. Any method to make this happen?
Below is a sample sandbox link including the code.
https://codesandbox.io/s/restless-wildflower-9pl09k?file=/src/Parent.js
export default function Parent(props) {
const [data, setData] = useState(0);
const onClick = () => {
setData(1);
console.log(data);
setData(2);
};
return (
<>
<button onClick={onClick}> Click here </button>
{data === 1 ? <div>Test1: {data}</div> : <div>Test2: {data}</div>}
</>
);
}

The setState function returned by useState does not directly update the state. Instead it is used to send the value that React will use during the next asynchronous state update. console.log is an effect so if you want to see data logged every time it is changed, you can use React.useEffect. Run the code below and click the 0 button several times to see the state changes and effects in your browser.
function App() {
const [data, setData] = React.useState(0)
React.useEffect(_ => console.log("data", data), [data])
return <button
onClick={_ => setData(data + 1)}
children={data}
/>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Your comment talks about a network request example. Custom hooks can be designed to accommodate complex use cases and keep your components easy to write.
function App() {
const [{fetching, error, data}, retry] = useAsync(_ => {
return fetch("https://random-data-api.com/api/users/random_user")
.then(res => res.json())
}, [])
if (fetching) return <pre>Loading...</pre>
if (error) return <pre>Error: {error.message}</pre>
return <div>
<button onClick={retry} children="retry" />
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
}
function useAsync(f, deps) {
const [state, setState] = React.useState({fetching: true})
const [ts, setTs] = React.useState(Date.now())
React.useEffect(_ => {
f()
.then(data => setState({fetching: false, data}))
.catch(error => setState({fetching: false, error}))
}, [...deps, ts])
return [
state,
_ => {
setState({fetching: true, error: null, data: null})
setTs(Date.now())
}
]
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

The reason the console.log(data) did not reflect the latest data is because of the React manages state. Calls to setState() are asynchronous, and if you want to rely on the new value of the state, the correct way is to pass a function of old state, returning the current state. ref. documentation

Related

using Fomik hook `useField` and `react-data-table-component` causes infinite loop

I am using react-data-table-component inside formik form to update new values. But the problem is whenever the MyTable component is re-rendered, the selectableRowSelected() callback is called, which triggers onSelectedRowsChange event in which I use helpers.setValue() to set value, which then makes MyTable component renders again. This whole process causes infinite loop, and I still don't have a solution for this.
function MyTable() {
const [data, setData] = useState([]);
const [field, meta, helpers] = useField({ name: "use" });
useEffect(() => {
fetch("https://reqres.in/api/users?page=1&per_page=3")
.then((res) => res.json())
.then((res) => setData(res.data));
}, []);
const handleChange = React.useCallback(({ selectedRows }) => {
let selectedIds = selectedRows.map(function (row) {
return parseInt(row.id);
});
selectedIds.sort();
console.log("🚀 ~ selectedIds", selectedIds);
// helpers.setValue(selectedIds, true); --> uncomment this will cause infinite loop.
}, []);
return (
<DataTable
title="User List"
columns={columns}
data={data}
selectableRows
selectableRowsHighlight
onSelectedRowsChange={handleChange}
selectableRowSelected={(row) => {
return meta.value.includes(parseInt(row.id));
}}
/>
);
}
CodeSandbox: https://codesandbox.io/s/goofy-tu-l1pxvb?file=/src/MyTable.jsx:375-1249
I think I've figured it out myself. But I will post it here in case anyone may encounter the same.
There is no way to stop this problem as the way RDT works is whenever selectableRowSelected is called, it will dispatch an action with type: SELECT_MULTIPLE_ROWS:
dispatch({
type: 'SELECT_MULTIPLE_ROWS',...
});
then in tableReducer, it returns toggleOnSelectedRowsChange as boolean value:
case 'SELECT_MULTIPLE_ROWS': {
...
return {
...state,
...,
toggleOnSelectedRowsChange,
};
which controls the trigger of onSelectedRowsChange event (as mentioned at comment in the source code):
useDidUpdateEffect(() => {
onSelectedRowsChange({ allSelected, selectedCount, selectedRows: selectedRows.slice(0) });
// onSelectedRowsChange trigger is controlled by toggleOnSelectedRowsChange state
}, [toggleOnSelectedRowsChange]);
Overall, solution for this problem is don't use formik with RDT for row selection, use another datatable lib.

Multiple subscriptions causing source stream to fire twice

I want to use RxJS to listen to clicks, perform a transaction, and keep track of the transaction status:
function performTransaction() {
const status = {
'0': 'pending',
'1': 'success'
}
return interval(1000).pipe(take(2), map((i) => status[`${i}`]))
}
const click$ = new Subject<void>()
const claimTxnState$ = click$.pipe(switchMap(() => {
console.log('performing transaction') // -> runs once which is expected
return performTransaction()
}))
claimTxnState$.subscribe((v) => {
console.log('claimTxnState', v);
})
export default function App() {
return (
<div className="App">
<button onClick={() => click$.next()}>click me</button>
</div>
);
}
This causes performing transaction to be output once, as is expected.
But I want to pipe claimTxnState$ to more places:
const claimIsPending$ = claimTxnState$.pipe(map((claim) => claim === 'pending'))
claimIsPending$.subscribe((v) => {
console.log('claimIsPending', v);
})
This now causes performing transaction to be output twice, which I understand because these are cold observables that get recreated on each subscription. But I don't want this. I only want my performTransaction to get called once. How can I achieve this?
Complete example.
I found the answer as I was typing the question. I need to use the share operator to convert the observable from cold to hot. This has the effect of sharing claimTxnState$ between all subscribers (i.e. .pipes(...)):
const claimTxnState$ = click$.pipe(
switchMap(() => {
console.log('performing transaction')
return performTransaction()
}),
share() // <- this is the interesting line
)
Sandbox.
More detail.

"Network interruption occurred. Reconnecting…" , How to potentially automatically reconnect when i minimize or maximize chabot

We have implemented bot framework-webchat to create a bot. Currently, we handle the minimize and maximize with the event passed in component (code shown below) but the challenge occurs when I minimize and then maximize the chatbot I am seeing 'Unable to connect' message and then it flashes away and if after an hour-long later if we minimize and maximize I am getting 'Network interruption occurred, Reconnecting...' How do I keep webchat potentially automatically reconnect when I minimize and maximize Chabot.
MaximizeChatWndow () {
if (this.state.token === null &&
this.state.productService === null) {
return
}
this.setState({
directLine: this.createDirectLine()
}, () => {
this.setState({
minimized: false,
newMessage: false,
userId: 'User_' + Math.random
})
})
this.checkExpandFlag = true
}
The component:
render () {
const {
state: { minimized, store }
} = this 
return (
<Row>
<Col md={12}>
<div>
{minimized ? (
<ChatDon
handleMaximizeButtonClick={this.handleMaximizeButtonClick}
/>
) : (
<ChatWin
handleMinimizeButtonClick={this.handleMinimizeButtonClick}
directLine={this.state.directLine}
userId={this.state.userId}
store={store}
/>
)}
</div>
</Col>
</Row>
)
}
It looks like you creating your directLine object in "MaximizeChatWndow()" which I think is the problem. In "MaximizeChatWndow()", you should be fetching your token and passing that to your web chat component. It is in the web chat component that you should use the token to call createDirectLine().
It appears that there have been various updates to the 06.recomposing-us/a.minimizable-web-chat sample. (The docs also look like they are out of date and no longer match the code). However, if comparing to the available sample code, you will want to do something like the following. Please look at the full code in the above link as I am only including the most relevant parts here.
When I tested, I had no issues with the conversation resetting or the network disconnecting/reconnecting.
MinimizableWebChat.js
import WebChat from './WebChat';
const MinimizableWebChat = () => {
[...]
const [token, setToken] = useState();
const handleFetchToken = useCallback(async () => {
if (!token) {
const res = await fetch('http://localhost:3500/directline/token', { method: 'POST' });
const { token } = await res.json();
setToken(token);
}
}, [setToken, token]);
[...]
return (
[...]
<WebChat
className="react-web-chat"
onFetchToken={handleFetchToken}
store={store}
styleSet={styleSet}
token={token}
/>
)
WebChat.js
const WebChat = ({ className, onFetchToken, store, token }) => {
const directLine = useMemo(() => createDirectLine({ token }), [token]);
[...]
useEffect(() => {
onFetchToken();
}, [onFetchToken]);
return token ? (
<ReactWebChat ...
);
};
Hope of help!

How to load AJAX in react

Im trying to get my json result into my react code
The code looks like the following
_getComments() {
const commentList = "AJAX JSON GOES HERE"
return commentList.map((comment) => {
return (
<Comment
author={comment.author}
body={comment.body}
avatarUrl={comment.avatarUrl}
key={comment.id} />);
});
}
How do i fetch AJAX into this?
First, to fetch the data using AJAX, you have a few options:
The Fetch API, which will work out of the box in some browsers (you can use a polyfill to get it working in other browsers as well). See this answer for an example implementation.
A library for data fetching (which generally work in all modern browsers). Facebook recommends the following:
superagent
reqwest
react-ajax
axios
request
Next, you need to use it somewhere in your React component. Where and how you do this will depend on your specific application and component, but generally I think there's two scenarios to consider:
Fetching initial data (e.g. a list of users).
Fetching data in response to some user interaction (e.g. clicking a
button to add more users).
Fetching initial data should be done in the life-cycle method componentDidMount(). From the React Docs:
var UserGist = React.createClass({
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
};
},
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function (result) {
var lastGist = result[0];
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}.bind(this));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
ReactDOM.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
Here they use jQuery to fetch the data. While that works just fine, it's probably not a good idea to use such a big library (in terms of size) to perform such a small task.
Fetching data in response to e.g. an action can be done like this:
var UserGist = React.createClass({
getInitialState: function() {
return {
users: []
};
},
componentWillUnmount: function() {
this.serverRequest && this.serverRequest.abort();
},
fetchNewUser: function () {
this.serverRequest = $.get(this.props.source, function (result) {
var lastGist = result[0];
var users = this.state.users
users.push(lastGist.owner.login)
this.setState({ users });
}.bind(this));
},
render: function() {
return (
<div>
{this.state.users.map(user => <div>{user}</div>)}
<button onClick={this.fetchNewUser}>Get new user</button>
</div>
);
}
});
ReactDOM.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
Lets take a look on the fetch API : https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Lets say we want to fetch a simple list into our component.
export default MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
lst: []
};
this.fetchData = this.fetchData.bind(this);
}
fetchData() {
fetch('url')
.then((res) => {
return res.json();
})
.then((res) => {
this.setState({ lst: res });
});
}
}
We are fetching the data from the server, and we get the result from the service, we convert is to json, and then we set the result which will be the array in the state.
You can use jQuery.get or jQuery.ajax in componentDidMount:
import React from 'react';
export default React.createClass({
...
componentDidMount() {
$.get('your/url/here').done((loadedData) => {
this.setState({data: loadedData});
});
...
}
First I'd like to use fetchAPI now install of ajax like zepto's ajax,the render of reactjs is asyn,you can init a state in the constructor,then change the state by the data from the result of fetch.

How to set initialValues based on async source such as an ajax call with redux-form

On the official pages and in the GitHub issues for redux-form there are more than one example of how to work with initialValues however I cannot find a single one that focuses on explaining how initialValues can be set in response to an asynchronous source.
The main case that I have in mind is something like a simple CRUD application where a user is going to edit some entity that already exists. When the view is first opened and the redux-form component is mounted but before the component is rendered the initialValues must be set. Lets say that in this example that the data is loaded on demand when the component is first mounted and rendered for the first time. The examples show setting initialValues based on hard coded values or the redux store state but none that I can find focus on how to set the initialValues based on something async like a call to XHR or fetch.
I'm sure I'm just missing something fundamental so please point me in the right direction.
References:
Initializing Form State
Handling form defaults
What is the correct way to populate a dynamic form with initial data?
EDIT: Updated Solution from ReduxForm docs
This is now documented in the latest version of ReduxForm, and is much simpler than my previous answer.
The key is to connect your form component after decorating it with ReduxForm. Then you will be able to access the initialValues prop just like any other prop on your component.
// Decorate with reduxForm(). It will read the initialValues prop provided by connect()
InitializeFromStateForm = reduxForm({
form: 'initializeFromState'
})(InitializeFromStateForm)
// now set initialValues using data from your store state
InitializeFromStateForm = connect(
state => ({
initialValues: state.account.data
})
)(InitializeFromStateForm)
I accomplished this by using the redux-form reducer plugin method.
The following demos fetching async data and pre-populating a user form with response.
const RECEIVE_USER = 'RECEIVE_USER';
// once you've received data from api dispatch action
const receiveUser = (user) => {
return {
type: RECEIVE_USER,
payload: { user }
}
}
// here is your async request to retrieve user data
const fetchUser = (id) => dispatch => {
return fetch('http://getuser.api')
.then(response => response.json())
.then(json => receiveUser(json));
}
Then in your root reducer where you include your redux-form reducer you would include your reducer plugin that overrides the forms values with the returned fetched data.
const formPluginReducer = {
form: formReducer.plugin({
// this would be the name of the form you're trying to populate
user: (state, action) => {
switch (action.type) {
case RECEIVE_USER:
return {
...state,
values: {
...state.values,
...action.payload.user
}
}
default:
return state;
}
}
})
};
const rootReducer = combineReducers({
...formPluginReducer,
...yourOtherReducers
});
Finally you include you combine your new formReducer with the other reducers in your app.
Note The following assumes that the fetched user object's keys match the names of the fields in the user form. If this is not the case you will need to perform an additional step on the data to map fields.
By default, you may only initialize a form component once via initialValues. There are two methods to reinitialize the form component with new "pristine" values:
Pass a enableReinitialize prop or reduxForm() config parameter set to true to allow the form the reinitialize with new "pristine" values every time the initialValues prop changes. To keep dirty form values when it reinitializes, you can set keepDirtyOnReinitialize to true. By default, reinitializing the form replaces all dirty values with "pristine" values.
Dispatch the INITIALIZE action (using the action creator provided by redux-form).
Referenced from : http://redux-form.com/6.1.1/examples/initializeFromState/
Could you fire the dispatch on componentWillMount(), and set the state to loading.
While it is loading, render a spinner for example and only when the request returns with the values, update the state, and then re-render the form with the values??
Here is minimal working example on how to set initialValues based on async source.
It uses initialize action creator.
All values from initialValues shouldn't be undefined, or you will get an infinite loop.
// import { Field, reduxForm, change, initialize } from 'redux-form';
async someAsyncMethod() {
// fetch data from server
await this.props.getProducts(),
// this allows to get current values of props after promises and benefits code readability
const { products } = this.props;
const initialValues = { productsField: products };
// set values as pristine to be able to detect changes
this.props.dispatch(initialize(
'myForm',
initialValues,
));
}
While this method may not be the best solution, it works well enough for my needs:
AJAX request to API on entry
Initializes form with data when request has been fulfilled or displays a server error
Resetting form will still reset to initial seed data
Allows the form to be reused for other purposes (for example, a simple if statement could bypass setting initial values): Add Post and Edit Post or Add Comment and Edit Comment...etc.
Data is removed from Redux form on exit (no reason to store new data in Redux since it's being re-rendered by a Blog component)
Form.jsx:
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { browserHistory, Link } from 'react-router';
import { editPost, fetchPost } from '../../actions/BlogActions.jsx';
import NotFound from '../../components/presentational/notfound/NotFound.jsx';
import RenderAlert from '../../components/presentational/app/RenderAlert.jsx';
import Spinner from '../../components/presentational/loaders/Spinner.jsx';
// form validation checks
const validate = (values) => {
const errors = {}
if (!values.title) {
errors.title = 'Required';
}
if (!values.image) {
errors.image = 'Required';
}
if (!values.description) {
errors.description = 'Required';
} else if (values.description.length > 10000) {
errors.description = 'Error! Must be 10,000 characters or less!';
}
return errors;
}
// renders input fields
const renderInputField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} className="form-details complete-expand" placeholder={label} type={type}/>
{touched && error && <div className="error-handlers "><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
</div>
</div>
)
// renders a text area field
const renderAreaField = ({ textarea, input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<textarea {...input} className="form-details complete-expand" placeholder={label} type={type}/>
{touched && error && <div className="error-handlers"><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
</div>
</div>
)
class BlogPostForm extends Component {
constructor() {
super();
this.state = {
isLoaded: false,
requestTimeout: false,
};
}
componentDidMount() {
if (this.props.location.query.postId) {
// sets a 5 second server timeout
this.timeout = setInterval(this.timer.bind(this), 5000);
// AJAX request to API
fetchPost(this.props.location.query.postId).then((res) => {
// if data returned, seed Redux form
if (res.foundPost) this.initializeForm(res.foundPost);
// if data present, set isLoaded to true, otherwise set a server error
this.setState({
isLoaded: (res.foundPost) ? true : false,
serverError: (res.err) ? res.err : ''
});
});
}
}
componentWillUnmount() {
this.clearTimeout();
}
timer() {
this.setState({ requestTimeout: true });
this.clearTimeout();
}
clearTimeout() {
clearInterval(this.timeout);
}
// initialize Redux form from API supplied data
initializeForm(foundPost) {
const initData = {
id: foundPost._id,
title: foundPost.title,
image: foundPost.image,
imgtitle: foundPost.imgtitle,
description: foundPost.description
}
this.props.initialize(initData);
}
// onSubmit => take Redux form props and send back to server
handleFormSubmit(formProps) {
editPost(formProps).then((res) => {
if (res.err) {
this.setState({
serverError: res.err
});
} else {
browserHistory.push(/blog);
}
});
}
renderServerError() {
const { serverError } = this.state;
// if form submission returns a server error, display the error
if (serverError) return <RenderAlert errorMessage={serverError} />
}
render() {
const { handleSubmit, pristine, reset, submitting, fields: { title, image, imgtitle, description } } = this.props;
const { isLoaded, requestTimeout, serverError } = this.state;
// if data hasn't returned from AJAX request, then render a spinner
if (this.props.location.query.postId && !isLoaded) {
// if AJAX request returns an error or request has timed out, show NotFound component
if (serverError || requestTimeout) return <NotFound />
return <Spinner />
}
// if above conditions are met, clear the timeout, otherwise it'll cause the component to re-render on timer's setState function
this.clearTimeout();
return (
<div className="col-sm-12">
<div className="form-container">
<h1>Edit Form</h1>
<hr />
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<Field name="title" type="text" component={renderInputField} label="Post Title" />
<Field name="image" type="text" component={renderInputField} label="Image URL" />
<Field name="imgtitle" component={renderInputField} label="Image Description" />
<Field name="description" component={renderAreaField} label="Description" />
<div>
<button type="submit" className="btn btn-primary partial-expand rounded" disabled={submitting}>Submit</button>
<button type="button" className="btn btn-danger partial-expand rounded f-r" disabled={ pristine || submitting } onClick={ reset }>Clear Values</button>
</div>
</form>
{ this.renderServerError() }
</div>
</div>
)
}
}
BlogPostForm = reduxForm({
form: 'BlogPostForm',
validate,
fields: ['name', 'image', 'imgtitle', 'description']
})(BlogPostForm);
export default BlogPostForm = connect(BlogPostForm);
BlogActions.jsx:
import * as app from 'axios';
const ROOT_URL = 'http://localhost:3001';
// submits Redux form data to server
export const editPost = ({ id, title, image, imgtitle, description, navTitle }) => {
return app.put(`${ROOT_URL}/post/edit/${id}?userId=${config.user}`, { id, title, image, imgtitle, description, navTitle }, config)
.then(response => {
return { success: response.data.message }
})
.catch(({ response }) => {
if(response.data.deniedAccess) {
return { err: response.data.deniedAccess }
} else {
return { err: response.data.err }
}
});
}
// fetches a single post from the server for front-end editing
export const fetchPost = (id) => {
return app.get(`${ROOT_URL}/posts/${id}`)
.then(response => {
return { foundPost: response.data.post}
})
.catch(({ response }) => {
return { err: response.data.err };
});
}
RenderAlert.jsx:
import React, { Component } from 'react';
const RenderAlert = (props) => {
const displayMessage = () => {
const { errorMessage } = props;
if (errorMessage) {
return (
<div className="callout-alert">
<p>
<i className="fa fa-exclamation-triangle" aria-hidden="true"/>
<strong>Error! </strong> { errorMessage }
</p>
</div>
);
}
}
return (
<div>
{ displayMessage() }
</div>
);
}
export default RenderAlert;
Reducers.jsx
import { routerReducer as routing } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
form: formReducer,
routing
});
export default rootReducer;
use this :
UpdateUserForm = reduxForm({
enableReinitialize: true,
destroyOnUnmount: false,
form: 'update_user_form' // a unique identifier for this form
})(UpdateUserForm);
UpdateUserForm = connect(
(state) => ({
initialValues: state.userManagment.userSingle
})
)(UpdateUserForm);
export default UpdateUserForm;

Resources