Cannot pass data between components in React/Redux - reactjs

I'm new to React and am trying to build an app which shuffles football players into two teams and am having difficulty with passing data from one component to another. I have redux and react-redux installed.
My problem is that once 10 names have been inputted (which I am calling numbersReached), the button to submit the addPlayers form should be disabled so no more players can be submitted.
I have tried passing a state value, numbersReached, into my AddPlayers component, but it is not being imported correctly - it is showing in console.log as undefined.
My code so far:
'initialState.js'
export const initialState = {
playersList: [],
shuffledList: [],
teamA: [],
teamB: [],
numbersReached: false
};
export default initialState;
'src\components\NumbersReached\index.js':
import { connect } from "react-redux";
import NumbersReached from "./NumbersReached";
const mapStateToProps = (state) => {
return {
...state,
numbersReached: state.playersList.length >= 10 // Once state.playersList.length>=10 numbersReached should = true. (10 is for five-a-side)
};
};
export default connect(mapStateToProps)(NumbersReached);
src\components\NumbersReached\NumbersReached.js:
import React from "react";
const NumbersReached = ({ numbersReached }) => (
<div>
{numbersReached ? "Numbers Reached" : null}
</div>
);
export default NumbersReached;
'src\components\AddPlayer\index.js':
import { connect } from "react-redux";
import AddPlayer from "./AddPlayer";
import { addPlayer } from "../../data/actions";
const mapStateToProps = (state) => {
return {
playerName: state.playerName,
numbersReached: state.numbersReached
};
};
const mapDispatchToProps = (dispatch) => {
return {
handleSubmit: (data) => dispatch(addPlayer(data)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AddPlayer);
'src\components\AddPlayer\AddPlayer.js'
import React, { Component } from 'react';
class AddPlayer extends Component {
constructor(props) {
super(props);
this.state = {
playerName: props.playerName,
numbersReached: props.numbersReached
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChange(e) {
this.setState({ playerName: e.currentTarget.value });
}
handleSubmit(e) {
e.preventDefault();
this.props.handleSubmit({ ...this.state });
}
render() {
return (
<React.Fragment>
<form className="entry-form" onSubmit={this.handleSubmit}>
<input
placeholder="Enter a player's name"
className="player-input"
type="text"
onChange={this.handleChange}
/>
<button
type="submit"
className="player-submit"
disabled={this.state.numbersReached} // Does not disable the button as this.state.numbersReached is undefined
>
Add a player
</button>
</form>
</React.Fragment>
)
}
};
export default AddPlayer;
Help is very much appreciated!

The problem you're likely having is that you're not actually using the value of numbersReached in your Redux store. Instead, you're using a numbersReached variable inner to your AddPlayer component' state.
<button
type="submit"
className="player-submit"
disabled={this.state.numbersReached}
>
To use the numbersReached you defined in mapStateToProps, you should access to this.props.numbersReached instead of this.state.numbersReached. The props you inherit from Redux will automatically be updated when the action addPlayer is dispatched and the Redux store changes.
The value for your this.state.numbersReached, however, won't change unless you call setState anywhere in your component. Right now, you're only initializing that value on the constructor:
class AddPlayer extends Component {
constructor(props) {
super(props);
this.state = {
playerName: props.playerName,
numbersReached: props.numbersReached
};
};
}
When you do this, you're creating a new numbersReached variable in your component' state with the value that the numbersReached property in your Redux store had at the time your component is built. As it doesn't have value when the constructor runs, this.state.numbersReached is undefined.
Doing numbersReached: props.numbersReached does not suscribe you to all of the changes to that property; it just assigns numbersReached the value props.numbersReached initially had. That's why, no matter how many players you add, it'll keep been undefined even if players have been succesfully added to your store.

you are duplicating redux state at AddPlayer state. duplicate state is bad practice and should be avoided. secondly, they are different states, redux'state and react component's state. as it is this.state.numbersReached receives the first value on mounting but is not not updated anywehere after.
Also it is weird at your state playerName: props.playerName. first, the fact that you dont have at your redux a piece of state called playerName. Second, the most revelant, it's a component that create players and adds to your playersList. you should better initialize that state as an empty string.
btw, I refactored a little your component to reduce code. if you declare your functions as arrow functions you dont need to bind them at constructor. and you dont need to declare your state at constructor anymore. But if you want to keep the original for consistency, or personal preference go ahead, that's ok also :) .
after all that, your component would look something like:
import React, { Component } from 'react';
class AddPlayer extends Component {
state = {
playerName: ''
};
handleChange = (e) => {
this.setState({ playerName: e.currentTarget.value });
}
handleSubmit = (e) => {
e.preventDefault();
this.props.handleSubmit({ ...this.state });
}
render() {
return (
<React.Fragment>
<form className="entry-form" onSubmit={this.handleSubmit}>
<input
placeholder="Enter a player's name"
className="player-input"
type="text"
onChange={this.handleChange}
/>
<button
type="submit"
className="player-submit"
disabled={this.props.numbersReached}
>
Add a player
</button>
</form>
</React.Fragment>
)
}
};
export default AddPlayer;
a last note, you have numbersReached state, but at your mapStateToProps you set based on a custom function, not on your numbersReached state.
if you want to keep numbersReached state on your redux state, you should handle that logic state.playersList.length >= 10 at your reducers to update properly there, and not at your mapStateToProps. though you could remove from your redux state numbersReached altogether given it's derived from other pieces from your state.

Related

Using react-hook-form in React with Typescript in child component

I am using a basic example of the react-hook-form library and even after looking up the documentary, I do not know how to pass the data from the form to another component. Here is my form component:
import { useForm, SubmitHandler } from "react-hook-form";
type FormInputs = {
minimalFrequency: number;
maximialFrequency: number;
};
// const onSubmit: SubmitHandler<FormInputs> = data => console.log(data);
export default function BasicUsage() {
const { register, formState: { errors }, handleSubmit, getValues } = useForm<FormInputs>({
defaultValues: {
min: 250,
max: 8000,
}
});
const onSubmit = (data: any) => {
console.log(data);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("minimalFrequency", { required: true })} />
{errors.minimalFrequency && "Only numbers are allowed"}
<input {...register("maximialFrequency", { required: true })} />
{errors.maximialFrequency && "Only numbers are allowed"}
<input type="submit" />
</form>
);
}
I would want to get the min and max values, in form of the given data object, of the user after they pushed the "submit" button and I just can't get my head around how it works.
My main component is a quite large class component, and I read that it might not work because react-hook-form needs a functional component. If true, is there a way to still use my class component somehow?
UPDATE: Added the parent component
import { useState } from "react";
import React from "react";
import BasicUsage from "./BasicUsage"
type Props = {
}
type State = {
dataFreq: object;
}
export default class Parent extends React.Component<Props, State>{
private timer: any;
constructor(props: Props) {
super(props);
this.state = {
dataFreq: {
minimalFrequency: 250,
maximialFrequency: 8000
}
};
}
getDataFromForm = (dataFreq: any) => {
this.setState({dataFreq: dataFreq })
console.log(dataFreq)
};
render() {
const minFreq = this.state.dataFreq;
console.log("This is a this dataFreq", this.state.dataFreq);
console.log("This is a this minimalFrequency", minFreq);
return (
<div>
<BasicUsage getDataFromForm={this.getDataFromForm}/>
</div>
);
}
}
You are still able to use your class component as a parent.
If I am I correct in assuming that you want to use data from the form in your main component, and the main component is the parent, you can define a function in your main component, something like
getDataFromForm(data){
this.setState({data: data })
}
Then you pass this function into your BasicUsage component
//In your main components render function, or wherever you are using the BasicUsage component
<BasicUsage
//other props you want to send into BasicUsage from the main component
getDataFromForm={getDataFromForm}
/>
Now in your BasicUsage component's onSubmit function you can call the function you passed as a prop as such
const onSubmit = (data: any) => {
//Do something with your data if you want to change format or process it somehow;
//in this case you should probably make a new variable and pass the new variable into getDataFromForm
props.getDataFromForm(data) //Call the function in the parent component
}
If you're using the form data in a sibling component and not a parent component, you would make the getDataFromForm function in a common parent and pass the function to the BasicUsage component and the state.data value into the sibling component where you want to access the data

what is the best way to share the state outside same component in react

I have encountered a problem and I am new to react. I wanted to find what is the best way to share react state outside of the same component for updating input value
function async callAjax(makeAjaxRequest){
//some ajax call
updateState();
return false;
}
function updateState() {
//I want to update state here from component and from outside
// component as well i.e call from callAjax function
//I wanted to submit form after state update, Can I pass formRef to
//chaining functions to submit or is there any better way?
}
export class test extends React.Component<testProps> {
constructor(props) {
super(props);
this.state = {
action: ''
}
AjaxRequest = await callAjax(
this.props.makeAjaxRequest
);
updateState();
render() {
<form>
<input type="hidden" value={this.state.action} />
</form>
}
}
I have done research around this found some like react sharedContext(useContext) but useContext is mostly used between different components for sharing data but I wanted inside single component. Can anyone help find best way to solve this problem?
Thanks in advance.
I think you shouldn't update the state of a component outside of the component as this may lead to problems. If you must have updateState outside of the component I think you can add callback which will be run when needed.
function async callAjax(makeAjaxRequest, stateCallback ){
updateState( stateCallback );
return false;
}
function updateState( stateCallback ) {
const newValue = 123
stateCallback( newValue )
}
export class Test extends React.Component<TestProps> {
constructor(props) {
super(props);
this.state = {
action: ''
}
}
AjaxRequest = await callAjax(
this.props.makeAjaxRequest,
( newValue ) => this.setState( newValue )
);
render() {
<form>
<input type="hidden" value={this.state.action} />
</form>
}
}
You can also find concept of Redux interesting.

Setting the initial state of a controlled input with a prop value that is initialized asynchronously

I have a controlled input that accepts a prop called user. This object is passed to it by its parent component, where it is initialized asynchronously with an observer1.
In the parent component:
onAuthStateChanged() {
this.unregisterAuthObserver = onAuthStateChanged((user) => {
this.setState({
isSignedIn: Boolean(user),
user,
});
});
}
I would like to populate the initial state of the controlled input with user.displayName. Something like the following, which shouldn't be an anti-pattern because the state is only dependent on the prop user on construction.
class ControlledInput extends Component {
constructor(props) {
super(props);
const { user } = props;
this.state = {
displayName: user ? user.displayName : '',
};
}
}
When I refresh the controlled input, the following problem occurs:
The value of user is undefined because the observer has yet to fire. The component is constructed and displayName is assigned to ''.
The observer fires, user is assigned a complex object, and React re-renders the component. This does not change the state of the component and displayName is still ''.
I'm stuck on how to utilize the component lifecycle methods to achieve this. Is there a best practice for this scenario? Should I even be dependent on an asynchronous prop, or should I move the observer into the controlled component?
I've considered using componentDidUpdate() to determine when user is assigned, but it feels like a hack.
Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
1 The observer is part of Firebase Authentication, but I'm not sure that's relevant to the question.
There is no need to assign in the constructor. You should use the props provided to the render method, and following the Lifting State Up pattern handle updates to the state in the parent. That will then flow down to the child component.
See the example below. The "increment" button simulates the async call. ControlledInput is a read-only display, while ControlledInput2 allows edits to the input that are reflected in state.
class Parent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.onInputChanged = this.onInputChanged.bind(this);
this.state = {
user,
count: 1
};
}
handleClick(e) {
const { user, count } = this.state;
this.setState({
user: { ...user, displayName: `display ${count + 1}` },
count: count + 1
});
}
onInputChanged(name) {
this.setState({
user: Object.assign(user, { displayName: name })
});
}
render() {
const { user, count } = this.state;
return (
<div>
<ControlledInput user={user} />
<div>{user.displayName}</div>
<div>Count: {count}</div>
<button onClick={this.handleClick}>increment</button>
<div>
<ControlledInput2 onInputChanged={this.onInputChanged} user={user} />
</div>
</div>
);
}
}
const ControlledInput = ({ user }) =>
<input type="text" value={user.displayName} />;
class ControlledInput2 extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onInputChanged(e.target.value);
}
render() {
const { user } = this.props;
return (
<input
type="text"
onChange={this.handleChange}
value={user.displayName}
/>
);
}
}
const user = { displayName: undefined };
const props = { user };
const rootElement = document.getElementById("sample");
ReactDOM.render(<Parent {...props} />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
<div id="sample"></div>

Adding Redux to an existing React app

I've been working on a React app and have gotten to a point where I'll need Redux to handle some aspects of it.
After reading a bunch of tutorials, I'm fairly stuck on how to make my "smarter" components "dumber" and move functions into my actions and reducers.
So, for example, one aspect of the app is more of a to-do list style.
One of my classes starts like this:
export default class ItemList extends React.Component {
constructor() {
super();
this.state = { items: [],
completed: [],
};
this.addItem = this.addItem.bind(this);
this.completeItem = this.completeItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
addItem(e) {
var i = this.state.items;
i.push({
text: this._inputElement.value,
paused: false,
key: Date.now()
});
this.setState({ items: i });
e.preventDefault();
this._inputElement.value = '';
this._inputElement.focus();
}
completeItem(e) {
this.deleteItem(e);
var c = this.state.completed;
c.push({
text: e.target.parentNode.parentNode.getElementsByClassName('item-name')[0].innerHTML,
paused: false,
key: Date.now()
});
this.setState({ completed: c });
}
deleteItem(e) {
var i = this.state.items;
var result = i.filter(function(obj) {
return obj.text !== e.target.parentNode.parentNode.getElementsByClassName('item-name')[0].innerHTML;
});
this.setState({ items: result });
}
// ... more irrelevant code here ...
// there's a function called createTasks that renders individual items
render() {
var listItems = this.state.items.map(this.createTasks);
return <div className="item-list">
<form className="form" onSubmit={this.addItem}>
<input ref={(a) => this._inputElement = a}
placeholder="Add new item"
autoFocus />
<button type="submit"></button>
</form>
{listItems}
</div>;
}
}
So, as you can see, it's very logic-heavy. I've started adding Redux by adding a <Provider> in my index file, and made a basic reducers file that is fairly empty so far:
import { combineReducers } from 'redux';
const itemList = (state = {}, action) => {
};
// ... other irrelevant reducers
const rootReducer = combineReducers({
itemList,
// ...
});
export default rootReducer;
...and I've made an actions file that doesn't have much in it yet either.
I've been struggling to figure out:
Most actions I've seen examples of just return some kind of JSON, what do I return in the reducer that uses that JSON that my component can use?
How much of my component logic is reusable, or should I just forget it? What is the best way to go about this to reuse as much code as I've written as possible?
First of all you need to understand the overall picture of how redux works with react.
Before coming to that lets first understand what are smart components and dumb components.
Smart Components
All your code logic needs to be handled here
They are also called containers.
They interact with the store(aka state management) to update your components.
Dumb Components
They just read props from your containers and render you components
This is just the UI view and should not contain any logic.
All styling/html/css comes in your dumb components.
Here is an amazing piece of article which you can go through to understand smart and dumb components if you still have doubts.
Ok, now lets try understanding how redux works:-
Your smart components(aka containers) interact with your redux store
You fire actions from your containers.
Your actions call your apis
The result of your action updates the store through a reducer
You containers read the store through mapStateToProps function and as soon as value in store changes it updates your component.
Now lets consider your todo example
TodoListContainer.js
class TodoListContainer extends Component {
componentWillMount () {
// fire you action action
}
render () {
return (
<Todos todos=={this.props.todos} />
)
}
}
function mapStateToProps(state) {
const {todos} = state;
return {
todos;
}
}
export default connect(mapStateToProps)(TodoListContainer)
TodoList.js
class TodoList extends Component {
renderTodos() {
return this.props.todos.map((todo)=>{
return <Todo todo={todo} key={todo.id} />
})
}
render () {
return () {
if (this.props.todos.length === 0) {
return <div>No todos</div>
}
return (
<div>
{this.renderTodos()}
</div>
)
}
}
}
export default class TodoList
Todo.js
class Todo extends Component {
render () {
return (
<div>
<span>{this.props.todo.id}</span>
<span>{this.props.todo.name}</span>
</div>
)
}
}
Reducer
export default function todos(state={},action) {
switch (action.type) {
case 'RECEIVE_TODOS':
return Object.assign(state,action.todos);
}
}
action
function fetchTodos() {
return(dispatch) => {
axios.get({
//api details
})
.then((res)=>{
dispatch(receiveTodos(res.todos))
})
.catch((err)=>{
console.warn(err)
})
}
}
function receiveTodos(todos) {
return {
type: 'RECEIVE_TODOS',
todos
}
}
Now if you have read redux documentation you would see that actions return objects then how would i call my api there which returns a function instead of an object. For that I used redux thunk about which you can read here.
I gave you an example in which you can fetch todos. If you want to do other operations like deleteTodo, addTodo, modifyTodo then you can do that in appropriate components.
DeleteTodo - you can do in TodoListContainer.
AddingTodo - you can do in TodoListContainer.
Changing State(completed/Pending) - you can do in TodoListContainer.
ModifyingTodo - you can do in TodoContainer.
You can also check out here for a detailed example, but before that I would say just should go through basics of redux which you can find here
P.S: I wrote code on the fly so it might not work properly but it should work with little modification.

React State Not Updating with Conditional Statement

I have this really strange issue with React.
The following below WORKS. It calls the action creator fetchUserForm which then fetches an object and sets it to the redux storage called userForm. userForm is then called in step1Component if it is loaded.
class FormEdit extends Component {
constructor(props) {
super(props)
this.nextPage = this.nextPage.bind(this)
this.previousPage = this.previousPage.bind(this)
this.updateComponents = this.updateComponents.bind(this)
this.state = {
page: 1,
}
}
componentWillMount() {
this.props.fetchUserForm(this.props.params.id);
}
render() {
const { page } = this.state
return (
<div>
{page === 1 && <Step1Page nextPage={this.nextPage}/>}
</div>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({fetchUserForm}, dispatch);
}
function mapStateToProps(state) {
return { userForm: state.newForm.userForm,
};
}
export default connect(null, mapDispatchToProps)(FormEdit);
Reducer:
const INITIAL_STATE = {
userForm:'false'
};
case FETCH_USER_FORM:
console.log("----------------> REDUCER: updating custom form");
console.log(action.payload);
return {...state, userForm: action.payload };
Step1Page Component:
render() {
if(!this.props.userForm){
return(
<div> LOADING </div>
)
}
return(
<div> Actual Content </div>
)
The above works perfectly. However, this is where my strange issue occurs. I want to do something with the userForm in the FormEdit component. I can't use the form until it fully loads so I add this to FormEdit:
if(!this.props.userForm){
return(
<div> LOADING </div>
)
}
return(
<div> "Actual Content </div>
)
EXCEPT when I add this to FormEdit, I'm stuck at the LOADING div forever. When I view the react tools, it says that the userForm is set to false.This makes no sense because when I view the console.log it says:
Which means it was passed to the reducer. However, even when it's passed, looking at react tools it says that the userForm is still "false".
IF I remove the conditional in FormEdit, everything works again and the userForm is filled with the objects. So I'm really confused why the conditional in my FormEdit component is causing such an issue. When it's not added, everything works fine. But when it is added, the reducer state remains false.
In FormEdit you don't have the userform property.
You have to pass mapStateToProps into the connect function.
export default connect(mapStateToProps, mapDispatchToProps)(FormEdit);

Resources